---
seo_title: Java SDK für Dateikonvertierung: Maven-Client | EnConvert
meta_desc: Offizielles EnConvert Java SDK für Java 17+. Eine Maven- oder Gradle-Abhängigkeit konvertiert Dateien und liest Webseiten per perceive, discover, distill, ingest und watch.
keywords: java sdk dateikonvertierung, dateien konvertieren java, maven client dateikonvertierung api, gradle bibliothek dateikonvertierung, url zu pdf java, docx in pdf konvertieren java, html in pdf umwandeln java, heic in webp konvertieren java, web scraping api java, webseite in markdown umwandeln java, rag pipeline java, enconvert java sdk
---

# Java SDK für Dateikonvertierung

`com.enconvert:enconvert-sdk` ist der offizielle Java-Client für die EnConvert API. Eine einzige Maven- oder Gradle-Abhängigkeit liefert dir die Datei-Konvertierung (URL zu PDF, DOCX zu PDF, HEIC zu WebP, alles zu Markdown) plus die V2-Oberfläche für Web-Intelligence: perceive, discover, lookup, distill, ingest und watch. Das SDK zielt auf Java 17 und neuer, läuft auf dem im JDK enthaltenen `java.net.http.HttpClient` und zieht Gson als einzige Drittanbieter-Abhängigkeit nach. Jeder Aufruf ist eine schlichte blockierende Methode, die ein typisiertes Record zurückgibt, und lange Konvertierungen erholen sich transparent von Reverse-Proxy-Timeouts, indem sie den Job-Status abfragen.

<div class="alert alert-info">
<strong>Maven Central:</strong> <code>com.enconvert:enconvert-sdk:0.0.1</code> · <strong>Quelle:</strong> <a href="https://github.com/conversionapi/java-sdk">conversionapi/java-sdk</a> · <strong>Java:</strong> 17+ · <strong>Abhängigkeiten:</strong> nur Gson
</div>

---

## Installation

```groovy
// build.gradle
dependencies {
    implementation 'com.enconvert:enconvert-sdk:0.0.1'
}
```

```kotlin
// build.gradle.kts
dependencies {
    implementation("com.enconvert:enconvert-sdk:0.0.1")
}
```

```xml
<!-- pom.xml -->
<dependency>
    <groupId>com.enconvert</groupId>
    <artifactId>enconvert-sdk</artifactId>
    <version>0.0.1</version>
</dependency>
```

HTTP wird vom `java.net.http.HttpClient` aus dem JDK erledigt. Das einzige Drittanbieter-Artefakt, das mitkommt, ist [Gson](https://github.com/google/gson) für JSON, deklariert als `api`-Abhängigkeit und damit auf deinem Compile-Classpath sichtbar.

---

## Schnellstart

```java
import com.enconvert.Enconvert;
import com.enconvert.model.ConversionResult;
import com.enconvert.model.UrlToPdfOptions;
import com.enconvert.model.v2.PerceiveOptions;
import com.enconvert.model.v2.PerceiveResult;

import java.util.List;

Enconvert client = new Enconvert(System.getenv("ENCONVERT_API_KEY"));

ConversionResult pdf = client.convertUrlToPdf("https://example.com",
        UrlToPdfOptions.builder().saveTo("page.pdf").build());
System.out.println(pdf.presignedUrl());

// Lies eine Seite so, wie es dein Agent tun sollte, mit angehängtem Quality-Score.
PerceiveResult page = client.v2.perceive("https://example.com",
        PerceiveOptions.builder().outputs(List.of("markdown", "structured")).build());
System.out.println(page.outputs().get("markdown").url());
System.out.println(page.renderQuality());   // z. B. 0.93
```

Jede Options-Klasse ist ein unveränderlicher Builder und jede Antwort ein Java-`record`, sodass sich Zugriffe als `pdf.presignedUrl()` und `page.renderQuality()` lesen. Der Client hält einen gemeinsamen `HttpClient` und keinen veränderlichen Zustand pro Anfrage, eine einzelne Instanz kann also ein Singleton oder eine über Threads geteilte Spring-Bean sein. Die Snippets unten lassen Imports weg: Options- und Antworttypen liegen in `com.enconvert.model` (Konvertierung) und `com.enconvert.model.v2` (Web-Intelligence), Exceptions in `com.enconvert.exceptions`.

---

## Was der Client bereitstellt

`Enconvert` trägt die Konvertierungs-Oberfläche direkt. Die Web-Intelligence-Oberfläche liegt auf dem öffentlichen finalen Feld `client.v2`, einer Instanz von `EnconvertV2`.

| Gruppe | Methoden | Rückgabe |
|-------|---------|---------|
| Einzelne URL | `convertUrlToPdf`, `convertUrlToScreenshot`, `convertUrlToMarkdown` | `ConversionResult` |
| Datei-Upload | `convertImage`, `convertDocument`, `convertToMarkdown`, `convertToPdf` | `ConversionResult` |
| Ganze Website | `convertWebsiteToPdf`, `convertWebsiteToScreenshot` | `BatchSubmission` |
| Status | `getJobStatus`, `getBatchStatus`, `waitForBatch` | `JobStatus`, `BatchStatus` |
| `v2` perceive | `perceive`, `perceiveDirect`, `getPerceiveOperation`, `perceiveBatch`, `getPerceiveBatch`, `downloadPerceiveArtifact` | `PerceiveResult`, `PerceiveDirectResult`, `PerceiveBatchResult` |
| `v2` discover | `discover` | `DiscoverResult` |
| `v2` lookup | `lookup` | `LookupResult` |
| `v2` distill | `distill` | `DistillResult` |
| `v2` ingest | `ingest`, `ingestFiles`, `getIngestJob`, `listIngestJobs`, `cancelIngestJob`, `retryIngestWebhook`, `getWebhookSecret`, `rotateWebhookSecret` | `IngestJob`, `IngestJobList`, `WebhookRetryResult`, `WebhookSecret` |
| `v2` watch | `createWatcher`, `getWatcher`, `listWatchers`, `getWatcherSnapshots`, `updateWatcher`, `deleteWatcher` | `Watcher`, `WatcherList`, `WatcherSnapshotList` |

Die meisten Methoden haben eine kurze Überladung ohne Options-Argument, `client.v2.perceive(url)` und `client.convertUrlToPdf(url)` kompilieren also beide. `convertImage`, `distill` und `ingest` sind die Ausnahmen: Jede nimmt immer ihr Options-Objekt entgegen, weil Zielformat, Schema beziehungsweise Quelle erforderlich sind.

---

## Datei-Konvertierung

Die Konvertierungs-Endpunkte decken 43 implementierte `{input}-to-{output}`-Paare ab, dazu zwei automatisch erkennende Endpunkte (`anything-to-markdown` und `anything-to-pdf`) sowie die Browser-Rendering-Endpunkte. Die vollständige Parameter-Referenz steht unter [Parameter und Optionen](/de/docs/parameters-options).

### convertUrlToPdf

Rendere jede öffentliche URL zu einem PDF.

```java
ConversionResult result = client.convertUrlToPdf("https://example.com",
        UrlToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder().pageSize("A4").orientation("landscape").build())
                .singlePage(false)
                .viewportWidth(1440)
                .saveTo("report.pdf")
                .build());
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `saveTo` | `String` | keiner | Lokaler Pfad, in den das PDF geschrieben wird. Übergeordnete Verzeichnisse werden für dich angelegt. |
| `singlePage` | `boolean` | `true` | `true` erzeugt eine einzige fortlaufende Seite. `false` paginiert anhand von `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions` | keiner | Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopf- und Fußzeile. Siehe [PDF-Optionen](#pdf-optionen). |
| `viewportWidth` | `int` | `1920` | Breite des Browser-Viewports in Pixeln. |
| `viewportHeight` | `int` | `1080` | Höhe des Browser-Viewports in Pixeln. |
| `loadMedia`, `enableScroll` | `boolean` | `true` | Vor der Erfassung auf Bilder und Videos warten und von oben nach unten scrollen, um Lazy-Loader auszulösen. |
| `outputFilename` | `String` | auto | Überschreibt den generierten Dateinamen. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth`, `List<BrowserCookie>`, `Map<String, String>` | keiner | Zugangsdaten, eingeschleuste Cookies und zusätzliche Request-Header für Seiten hinter einem Login. |

### convertUrlToScreenshot

Erfasse ein PNG einer beliebigen URL. Dieselben Optionen für Viewport, Medien, Scrollen, Dateiname, Auth, Cookies und Header wie bei `convertUrlToPdf`, ohne `singlePage` und `pdfOptions`.

```java
client.convertUrlToScreenshot("https://example.com",
        UrlToScreenshotOptions.builder().viewportWidth(1440).saveTo("screenshot.png").build());
```

### convertUrlToMarkdown

Extrahiere sauberes GitHub Flavored Markdown aus einer URL. Navigation, Fußzeilen, Werbung und Skripte werden entfernt, der Haupttext des Artikels bleibt erhalten, und YAML-Frontmatter (Titel, Beschreibung, URL, Links, Bilder) wird vorangestellt. Gleicher Optionssatz wie bei `convertUrlToScreenshot`.

```java
client.convertUrlToMarkdown("https://example.com/article",
        UrlToMarkdownOptions.builder().saveTo("article.md").build());
```

### convertImage

Konvertiere zwischen `jpeg`, `png`, `svg`, `heic` und `webp` oder rastere ein PDF zu JPEG.

```java
// Von einem Pfad auf der Festplatte
client.convertImage(Path.of("photo.heic"),
        ConvertImageOptions.builder("webp").saveTo("photo.webp").build());

// Ein PDF rastern
client.convertImage(Path.of("scan.pdf"),
        ConvertImageOptions.builder("jpeg").saveTo("scan.jpeg").build());

// Aus Bytes im Speicher mit explizitem Dateinamen
byte[] bytes = Files.readAllBytes(Path.of("photo.heic"));
client.convertImage(new FileInput(bytes, "photo.heic"),
        ConvertImageOptions.builder("webp").build());
```

Jede Datei-Methode kennt drei Eingabe-Überladungen: `java.nio.file.Path` (von der Festplatte gelesen), rohe `byte[]` (der Dateiname ist standardmäßig `upload.bin`) und `com.enconvert.FileInput`, wenn du Bytes im Speicher mit einem echten Dateinamen kombinieren musst. Das Eingabeformat wird aus der Erweiterung aufgelöst, das Ausgabeformat ist erforderlich.

| Option | Typ | Erforderlich | Beschreibung |
|--------|------|----------|-------------|
| `outputFormat` | `String` | Ja | Wird an `ConvertImageOptions.builder(outputFormat)` übergeben. Eines von `jpeg`, `png`, `svg`, `heic`, `webp`. Aliasse wie `jpg` werden normalisiert. |
| `saveTo` | `String` | nein | Lokaler Pfad, in den das Ergebnis geschrieben wird. |
| `outputFilename` | `String` | nein | Überschreibt den generierten Dateinamen. |

### convertDocument

Konvertiere Dokumente und strukturierte Datenformate. Das Ausgabeformat ist standardmäßig `pdf`.

```java
// docx zu pdf
client.convertDocument(Path.of("report.docx"),
        ConvertDocumentOptions.builder().saveTo("report.pdf").build());

// json zu yaml
client.convertDocument(Path.of("data.json"),
        ConvertDocumentOptions.builder().outputFormat("yaml").saveTo("data.yaml").build());

// markdown zu pdf mit Seiteneinrichtung
client.convertDocument(Path.of("README.md"),
        ConvertDocumentOptions.builder()
                .outputFormat("pdf")
                .pdfOptions(PdfOptions.builder()
                        .pageSize("A4")
                        .margins(new PdfMargins(20.0, 20.0, 25.0, 25.0))
                        .build())
                .saveTo("readme.pdf")
                .build());
```

**Erkannte Eingabe-Erweiterungen:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`.

EPUB hat kein eigenes Dokumentpaar. Schicke `.epub`-Dateien stattdessen durch [`convertToPdf`](#converttopdf) oder [`convertToMarkdown`](#converttomarkdown).

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `outputFormat` | `String` | `"pdf"` | Zielformat. |
| `saveTo` | `String` | keiner | Lokaler Pfad, in den das Ergebnis geschrieben wird. |
| `outputFilename` | `String` | keiner | Überschreibt den generierten Dateinamen. |
| `pdfOptions` | `PdfOptions` | keiner | Seiteneinrichtung, wird berücksichtigt, wenn die Ausgabe ein PDF ist. |

### Unterstützte Konvertierungen

`convertImage` und `convertDocument` prüfen das `{input}-to-{output}`-Paar gegen die Endpunkte, die die API tatsächlich implementiert. Ein nicht unterstütztes Paar wirft sofort eine `IllegalArgumentException` und listet die gültigen Ausgaben für diese Eingabe auf, statt für eine aussichtslose Anfrage einen Roundtrip zu bezahlen.

| Eingabe | Ausgaben |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | untereinander, alle 20 Paare |
| `pdf` | `jpeg` |

Dieselbe Tabelle lässt sich zur Laufzeit über `com.enconvert.Formats` abfragen: `Formats.validOutputsFor("json")` gibt `[csv, toml, xml, yaml]` zurück, `Formats.validOutputsFor("pdf")` gibt `[jpeg]` zurück, und `Formats.IMPLEMENTED_CONVERSIONS` enthält alle 43 Endpunktnamen.

### convertToMarkdown

Schicke ein beliebiges unterstütztes Dokument durch einen automatisch erkennenden Endpunkt und erhalte sauberes Markdown zurück. Die Überschriften-Hierarchie bleibt erhalten, was diesen Schritt zu einer natürlichen ersten Stufe für eine RAG-Pipeline macht.

```java
client.convertToMarkdown(Path.of("handbook.docx"),
        ConvertToMarkdownOptions.builder().saveTo("handbook.md").build());
```

Akzeptiert PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere und ODF-Office-Formate. Das Format wird serverseitig erkannt, es gibt also keine clientseitige Prüfung der Erweiterung: Jede Datei wird unverändert hochgeladen. Bilder werden nicht unterstützt und mit `400` abgelehnt. Die einzigen Optionen sind `saveTo` und `outputFilename`.

### convertToPdf

Der andere automatisch erkennende Endpunkt: fast alles zu PDF.

```java
// pptx zu pdf
client.convertToPdf(Path.of("slides.pptx"),
        ConvertToPdfOptions.builder().saveTo("slides.pdf").build());

// pdf-Durchreichung, in Graustufen umgewandelt
client.convertToPdf(Path.of("scan.pdf"),
        ConvertToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder().grayscale(true).build())
                .saveTo("scan-gray.pdf")
                .build());
```

Akzeptiert Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, reinen Text, Rasterbilder, SVG, EPUB und ein bestehendes PDF als Durchreichung. EPUB wird hier behandelt, weil es kein eigenes Dokumentpaar hat. Die Optionen sind `saveTo`, `outputFilename` und `pdfOptions`.

<div class="alert alert-warning">
<strong>Nur <code>grayscale</code> wird an diesem Endpunkt berücksichtigt.</strong> Die Seitengeometrie (Seitengröße, Breite und Höhe, Ausrichtung, Ränder, Skalierung, Kopfzeile, Fußzeile) wird von <code>anything-to-pdf</code> ignoriert. Wenn du die vollständige Seiteneinrichtung brauchst, nimm stattdessen <code>convertDocument</code> oder <code>convertUrlToPdf</code>.
</div>

### Konvertierung ganzer Websites

`convertWebsiteToPdf` und `convertWebsiteToScreenshot` ermitteln jede Seite einer Website, konvertieren jede einzelne im Hintergrund und bündeln die Ergebnisse in einem einzigen ZIP. Beide arbeiten asynchron und geben ein `BatchSubmission` zurück. Beide benötigen einen privaten API-Key.

```java
BatchSubmission batch = client.convertWebsiteToPdf("https://example.com",
        WebsiteToPdfOptions.builder()
                .crawlMode("sitemap")                      // "auto" (Standard), "sitemap", "full"
                .excludePatterns(List.of("/blog/tag/"))    // nur im Full-Crawl-Modus
                .notificationEmail("ops@example.com")
                .build());

System.out.println(batch.batchId() + " " + batch.urlCount() + " " + batch.discoveryMethod());

// Blockiert, bis der Batch "processing" verlässt, dann das ZIP speichern
BatchStatus status = client.waitForBatch(batch.batchId(),
        WaitForBatchOptions.builder().saveTo("site.zip").build());
System.out.println(status.completed() + " of " + status.total() + " pages converted");
```

`convertWebsiteToScreenshot` funktioniert identisch und erzeugt ein ZIP mit PNGs. `waitForBatch` fragt standardmäßig alle 5 Sekunden ab, gibt nach 30 Minuten auf und akzeptiert `intervalMs`, `timeoutMs` und `saveTo`. Bei Zeitüberschreitung wirft es eine `ApiException` mit Status `504`.

### Status selbst abfragen

```java
JobStatus job = client.getJobStatus("job_abc123");
if ("success".equals(job.status())) System.out.println(job.presignedUrl());
if ("failed".equals(job.status())) System.err.println(job.error());
BatchStatus batch = client.getBatchStatus("bat_abc123");
if (!"processing".equals(batch.status())) System.out.println(batch.zipDownloadUrl());
```

---

## Web-Intelligence (V2)

Alles unter `client.v2` verwandelt Webseiten in agentenfertige Daten. Jeder Lesevorgang trägt `renderQuality`, einen Wert von 0.0 bis 1.0, der angibt, wie sauber die Seite tatsächlich gerendert wurde. Eine Challenge-Seite, eine Cookie-Wall oder eine leere SPA-Hülle kommt mit niedrigem Wert sowie gefüllten `warnings()` und `deductions()` zurück, statt als echter Inhalt durchzugehen. So gelangt ein schlechter Lesevorgang nie unbemerkt in den Kontext deines Agenten. Der Inhalt wird trotzdem zurückgegeben, er ist lediglich markiert. Hintergründe zum Modell findest du in der [V2-Übersicht](/de/docs/v2-overview).

### Perceive

Rendere eine URL in genau die Artefakte, die du anforderst. Endpunkt-Referenz: [Perceive](/de/docs/v2-perceive).

```java
PerceiveResult page = client.v2.perceive("https://example.com",
        PerceiveOptions.builder()
                .outputs(List.of("markdown", "screenshot", "structured"))
                .extract(List.of("tables", "metadata"))
                .waitFor("css:main")
                .build());

System.out.println(page.renderQuality());                 // 0.0 bis 1.0
System.out.println(page.statusCode() + " " + page.deductions());  // z. B. 200 {http_error=0.7}
System.out.println(page.outputs().get("markdown").url()); // signierte URL, 15 Minuten
System.out.println(page.structured());                    // vom Aufrufer definierte Struktur
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `outputs` | `List<String>` | `["markdown", "structured"]` | Beliebige aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `List<String>` | keiner | Heuristische Ziele: `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `schema` | `Map<String, Object>` | keiner | JSON-Schema für die strukturierte Extraktion. |
| `waitFor`, `waitTimeoutMs` | `String`, `int` | keiner, `30000` | Ein CSS-Selektor, optional mit dem Präfix `css:`, oder `js:<expr>`, auf den gewartet wird, mit einem Budget von 0 bis 60000 ms. |
| `jsCode` | `String` | keiner | JavaScript, das nach der Navigation ausgeführt wird, maximal 20000 Zeichen. |
| `viewport`, `mobile` | `PerceiveViewport`, `boolean` | 1920 x 1080, `false` | Breite 320 bis 3840, Höhe 240 bis 2160, oder Mobile-Emulation. |
| `onlyMainContent` | `boolean` | `true` | Entfernt Navigation, Header, Footer und Cookie-Banner aus dem Markdown-Artefakt und dem `main_content`-Extrakt. |
| `cacheMode` | `String` | `"enabled"` | `enabled` nutzt einen Cache von 1 Stunde erneut, `bypass` überspringt ihn, `refresh` erzwingt ein erneutes Rendern. |
| `blockResources` | `List<String>` | keiner | Ressourcentypen, die der Browser nicht laden soll, zum Beispiel `image`, `font`, `script`. |
| `pdfOptions` | `PdfOptions` | keiner | Nur sinnvoll, wenn `outputs` den Wert `pdf` enthält. |
| `headers`, `cookies`, `auth` | `Map`, `List<BrowserCookie>`, `HttpBasicAuth` | keiner | Request-Header, eingeschleuste Cookies, HTTP-Basic-Zugangsdaten. |
| `respectRobots` | `boolean` | keiner | Berücksichtigt die Robots-Regeln der Website. |

<div class="alert alert-warning">
<strong>Noch nicht angebunden.</strong> <code>proxyUrl</code>, <code>geolocation</code> und <code>actionChain</code> existieren im Builder, sind serverseitig aber nicht verfügbar und werden derzeit mit <code>422</code> abgelehnt.
</div>

Artefakt-URLs sind 15 Minuten lang signiert und werden bei jedem Lesen der Operation neu signiert, `client.v2.getPerceiveOperation(page.operationId())` liefert dir also frische Links. Bündle bis zu 1000 URLs mit einem gemeinsamen Options-Block: Kleine Batches werden inline fertig, größere kommen mit dem Status `queued` zurück, frage sie also ab.

```java
PerceiveBatchResult batch = client.v2.perceiveBatch(
        List.of("https://a.example.com", "https://b.example.com"),
        PerceiveBatchOptions.builder()
                .outputs(List.of("markdown"))
                .outputMode("zip")          // "manifest" (Standard) oder "zip"
                .build());

PerceiveBatchResult done = client.v2.getPerceiveBatch(batch.jobId());
System.out.println(done.completed() + "/" + done.total());
```

Überspringe den Umweg über die signierte URL vollständig mit `perceiveDirect`, das die Artefakt-Bytes zurückstreamt. Es benötigt genau eine artefakterzeugende Ausgabe (alles außer `structured`) und wirft vor dem Senden eine `IllegalArgumentException`, wenn du mehr oder weniger anforderst:

```java
PerceiveDirectResult direct = client.v2.perceiveDirect("https://example.com",
        PerceiveOptions.builder().outputs(List.of("pdf")).build());

Files.write(Path.of(direct.filename()), direct.content());
System.out.println(direct.renderQuality() + " " + direct.contentType());

// Ein gespeichertes Artefakt einer früheren Operation erneut herunterladen
PerceiveDirectResult stored = client.v2.downloadPerceiveArtifact(page.operationId(), "markdown");
```

`downloadPerceiveArtifact` akzeptiert einen null-Wert oder einen weggelassenen Ausgabenamen, wenn die Operation genau ein Artefakt erzeugt hat, andernfalls gibt es `400` zurück und listet die verfügbaren Ausgaben auf. Sobald das gespeicherte Artefakt abgelaufen ist, gibt es `410` zurück.

### Discover

Zähle die URLs einer Website auf, ohne irgendetwas zu rendern. Es ist kein Browser beteiligt, daher ist es schnell. Endpunkt-Referenz: [Discover](/de/docs/v2-discover).

```java
DiscoverResult found = client.v2.discover("https://example.com",
        DiscoverOptions.builder()
                .mode("hybrid")                        // "sitemap", "crawl", "hybrid"
                .maxUrls(200)
                .maxDepth(3)
                .excludePatterns(List.of("/tag/"))
                .build());

System.out.println(found.total() + " urls, truncated=" + found.truncated());
found.urls().forEach(System.out::println);
```

| Option | Typ | Standard | Bereich |
|--------|------|---------|-------|
| `mode` | `String` | `"hybrid"` | `sitemap`, `crawl`, `hybrid` |
| `maxUrls` | `int` | `100` | 1 bis 1000 |
| `maxDepth` | `int` | `2` | 1 bis 5 |
| `includePatterns`, `excludePatterns` | `List<String>` | keiner | Regex-Positivliste und Sperrliste, jeweils maximal 50 Einträge. Die Sperrliste wird als zweites angewendet. |
| `sameDomainOnly`, `respectRobots` | `boolean` | `true`, keiner | Auf der Ausgangsdomain bleiben und die Robots-Regeln der Website berücksichtigen. |

### Lookup

Kategorisierte Websuche, die die besten Treffer auf Wunsch automatisch rendert. Endpunkt-Referenz: [Lookup](/de/docs/v2-lookup).

```java
LookupResult search = client.v2.lookup("best static site generators",
        LookupOptions.builder()
                .category("web")        // web, news, images, scholar, patents, maps
                .numResults(10)
                .country("us")
                .timeFilter("month")    // hour, day, week, month, year
                .perceiveTop(3)         // die 3 besten Treffer automatisch rendern
                .build());

search.results().forEach(hit -> {
    System.out.println(hit.position() + " " + hit.title() + " " + hit.url());
    if (hit.perceive() != null) System.out.println("  quality " + hit.perceive().renderQuality());
});
```

Mit `perceiveTop` über 0 (0 bis 10, Standard 0) werden die URLs der ersten N Treffer über perceive gerendert, und jeder Treffer trägt sein vollständiges `PerceiveResult` inline auf `hit.perceive()`. `numResults` läuft von 1 bis 100 und ist standardmäßig 10; `page` läuft von 1 bis 10.

### Distill

Schemagesteuerte strukturierte Extraktion über eine oder mehrere Seiten. Endpunkt-Referenz: [Distill](/de/docs/v2-distill).

```java
DistillResult extraction = client.v2.distill(
        DistillOptions.builder(Map.of("products", "list of product names with their listed price"))
                .urls(List.of("https://example.com/catalog"))
                .cssSchema(CssSchema.builder(".product-card", List.of(
                                CssField.builder("name", "text").selector("h3").build(),
                                CssField.builder("price", "text").selector(".price").build()))
                        .targetField("products")
                        .build())
                .build());

extraction.results().forEach(item ->
        System.out.println(item.data() + " via " + item.extractionTier()));
```

Das optionale `cssSchema` führt zuerst einen kostenlosen CSS-Durchlauf aus; nur die Felder, die es nicht beantworten kann, steigen in die LLM-Stufe auf, und `item.extractionTier()` meldet, welcher Weg den Datensatz erzeugt hat (`css`, `llm`, `mixed` oder `none`). Du kannst die URLs auch zuerst ermitteln lassen, statt sie aufzulisten:

```java
client.v2.distill(
        DistillOptions.builder(Map.of("title", "page title", "summary", "one-line summary"))
                .discoverFrom(new DistillDiscoverFrom("https://example.com", "sitemap", 10))
                .build());
```

Genau eines von `urls` (maximal 50) und `discoverFrom` muss gesetzt sein, und `schema` ist von der Builder-Factory vorgeschrieben. Beide Regeln werden clientseitig geprüft und werfen eine `IllegalArgumentException`, bevor eine Anfrage rausgeht.

### Ingest

Verwandle eine ganze Website oder einen Satz hochgeladener Dokumente über eine einzige Pipeline in gechunktes, RAG-fertiges JSONL. Ingest arbeitet immer asynchron. Endpunkt-Referenz: [Ingest](/de/docs/v2-ingest).

```java
// Von einer Website
IngestJob job = client.v2.ingest(IngestOptions.builder()
        .mode("sitemap")                                  // "urls" (Standard), "sitemap", "crawl"
        .url("https://docs.example.com")
        .maxPages(100)
        .chunk(new IngestChunkOptions(512, 1))            // maxWords, sentenceOverlap
        .webhookUrl("https://my.app/hooks/enconvert")
        .build());

// Oder aus hochgeladenen Dateien
IngestJob fileJob = client.v2.ingestFiles(
        List.of(new FileInput(Files.readAllBytes(Path.of("handbook.pdf")), "handbook.pdf"),
                new FileInput(Files.readAllBytes(Path.of("notes.docx")), "notes.docx")),
        IngestFilesOptions.builder().chunk(new IngestChunkOptions(512, 1)).build());

// Auf das JSONL pollen
IngestJob status = client.v2.getIngestJob(job.jobId());
if ("completed".equals(status.status())) {
    System.out.println(status.outputUrl() + " (" + status.totalChunks() + " chunks)");
}

client.v2.listIngestJobs(V2ListOptions.builder().limit(20).build());
client.v2.cancelIngestJob(job.jobId());   // idempotent
```

`ingestFiles` akzeptiert PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere und ODF-Office-Formate. Das Chunking liegt standardmäßig bei 512 Wörtern (32 bis 4000) mit 1 Satz Überlappung (0 bis 10). `mode` ist standardmäßig `urls`, was eine nicht leere `urls`-Liste verlangt und `url` verbietet; jeder andere Modus verlangt eine Start-`url` und verbietet `urls`. Das SDK erzwingt diese Paarung vor dem Senden.

Abschluss-Webhooks sind HMAC-signiert. Hole dir das Secret und die Header-Namen, die du zur Verifikation einer Zustellung brauchst, rotiere es bei einem Leck und stoße eine Zustellung neu an, die dein Endpunkt verpasst hat:

```java
WebhookSecret secret = client.v2.getWebhookSecret();
System.out.println(secret.signatureHeader() + " " + secret.signatureScheme());

client.v2.rotateWebhookSecret();          // alte Signaturen verifizieren sofort nicht mehr

WebhookRetryResult retry = client.v2.retryIngestWebhook(job.jobId());
System.out.println(retry.delivered() + " after " + retry.attempts() + " attempts");
```

### Watch

Wiederkehrende Änderungsüberwachung einer URL, mit Benachrichtigung per E-Mail und Webhook. Endpunkt-Referenz: [Watch](/de/docs/v2-watch).

```java
Watcher watcher = client.v2.createWatcher("https://example.com/pricing",
        WatchCreateOptions.builder()
                .frequencyMinutes(60)      // 60 bis 43200, Untergrenze eine Stunde
                .diffMode("auto")          // auto, text, structured, tables, metadata
                .webhookUrl("https://my.app/hooks/changes")
                .notifyEmail(true)
                .build());

WatcherSnapshotList history = client.v2.getWatcherSnapshots(watcher.watcherId(),
        SnapshotListOptions.builder().limit(10).build());
history.snapshots().forEach(s ->
        System.out.println(s.checkedAt() + " changed=" + s.hasChanges()
                + " similarity=" + s.similarity()));

client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().status("paused").build());
client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().webhookUrl("").build());
client.v2.deleteWatcher(watcher.watcherId());   // Soft Delete, idempotent
```

`listWatchers()` und `getWatcher(watcherId)` lesen sie wieder aus. `updateWatcher` verlangt mindestens ein Feld und wirft andernfalls eine `IllegalArgumentException`. Ein ausdrücklich leerer String für `webhookUrl` löscht den Webhook, während ein null-Wert "keine Änderung" bedeutet. `deleteWatcher` ist ein Soft Delete: Es gibt den als gelöscht markierten Watcher mit Status `deleted` zurück, und ein gelöschter Watcher liest sich danach als `404`.

<div class="alert alert-warning">
<strong>Snapshot-Diffs enthalten nicht vertrauenswürdige Seiteninhalte.</strong> <code>WatcherSnapshot.changes()</code> ist Rohtext, der aus der überwachten Seite stammt. Escape ihn, bevor du ihn in einem Dashboard, einer E-Mail oder einer Chat-Nachricht darstellst.
</div>

---

## PDF-Optionen

`PdfOptions` wird von `convertUrlToPdf`, `convertWebsiteToPdf`, `convertDocument`, `convertToPdf` und `PerceiveOptions.pdfOptions` gemeinsam genutzt.

```java
client.convertUrlToPdf("https://internal.example.com/report",
        UrlToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder()
                        .pageSize("A4")
                        .orientation("landscape")
                        .margins(new PdfMargins(10.0, 10.0, 15.0, 15.0))
                        .scale(0.9)
                        .header(new PdfHeaderFooter("Quarterly Report", 15.0))
                        .footer(new PdfHeaderFooter("Confidential", 12.0))
                        .build())
                .auth(new HttpBasicAuth("user", "pass"))
                .cookies(List.of(BrowserCookie.builder("session", "abc123").domain("internal.example.com").build()))
                .headers(Map.of("X-Tenant", "acme"))
                .saveTo("report.pdf")
                .build());
```

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `pageSize` | `String` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` und Ähnliches. |
| `pageWidth`, `pageHeight` | `double` | Eigene Maße. Gemeinsam gesetzt überschreiben sie `pageSize`. |
| `orientation` | `String` | `"portrait"` oder `"landscape"`. |
| `margins` | `PdfMargins` | Record aus `top`, `bottom`, `left`, `right`. Jedes null-Feld wird weggelassen. |
| `scale` | `double` | Render-Skalierung, zum Beispiel `0.9` für 90 Prozent. |
| `grayscale` | `boolean` | Wandelt das PDF nachträglich in Graustufen um. |
| `header`, `footer` | `PdfHeaderFooter` | Record aus `content` (maximal 2000 Zeichen) und `height`. |

`BrowserCookie` braucht Name und Wert sowie entweder `domain` oder `url`; wenn `domain` ohne `path` gesetzt ist, setzt die API `path` standardmäßig auf `/`. Kombiniere `auth` nicht mit einem expliziten `Authorization`-Header, denn die API lehnt diesen Konflikt ab.

---

## Fehlerbehandlung

Jede SDK-Exception erbt von `EnconvertException`, das wiederum von `RuntimeException` erbt, nichts erzwingt an deinen Aufrufstellen also eine `throws`-Klausel. Fange die spezifischen Unterklassen zuerst ab.

```java
try {
    client.convertUrlToPdf("https://example.com");
} catch (AuthenticationException e) {
    System.err.println("Invalid or missing API key");
} catch (QuotaException e) {
    System.err.println("Request refused with 402: " + e.getMessage());
} catch (RateLimitException e) {
    System.err.println("Too many requests, back off and retry");
} catch (ApiException e) {
    System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage());
}
```

| Klasse | Ausgelöst bei | Statuscode |
|-------|-----------|-------------|
| `AuthenticationException` | Fehlender, ungültiger oder nicht berechtigter API-Key | `401`, `403` |
| `QuotaException` | HTTP 402 | `402` |
| `RateLimitException` | Zu viele Anfragen | `429` |
| `ApiException` | Jede andere 4xx- oder 5xx-Antwort | der tatsächliche Code |
| `EnconvertException` | Basisklasse, wird auch bei Transportfehlern, einer unterbrochenen Anfrage oder einer nicht lesbaren Eingabedatei ausgelöst | keiner |

Clientseitige Validierung (ein nicht unterstütztes Konvertierungspaar, ein fehlendes distill-Schema, ein leeres Watcher-Update, die falsche Anzahl Ausgaben für `perceiveDirect`) wirft eine `IllegalArgumentException`, bevor eine Anfrage gestellt wird. Die Zuordnung der Meldungen für Serverantworten findest du in der Referenz [Fehlercodes](/de/docs/error-codes).

---

## Timeout-Recovery

Lange URL-Renders und große Dokumentkonvertierungen können das Reverse-Proxy-Timeout überdauern, selbst wenn die Konvertierung am Ende erfolgreich ist. Das SDK behandelt das transparent:

1. Vor jeder Anfrage für eine einzelne URL oder einen Datei-Upload erzeugt das SDK eine UUID und sendet sie als `job_id`.
2. Kommt die Anfrage mit 5xx zurück, wechselt das SDK dazu, `GET /v1/convert/status/{jobId}` alle 3 Sekunden abzufragen.
3. Bei `success` gibt es das Ergebnis zurück. Bei `failed` wirft es eine `ApiException` mit der Fehlermeldung des Servers.
4. Die Polling-Frist beträgt 5 Minuten. Danach wirft es `ApiException(504, "Conversion timed out")`.

Du schreibst dafür keinen Code. Wenn eine erfolgreiche Antwort `job_id` weglässt, trägt das SDK die selbst erzeugte ID nach, `result.jobId()` ist also immer mit `getJobStatus` verwendbar.

<div class="alert alert-info">
<strong>Website-Batch-Übermittlungen sind bewusst ausgenommen.</strong> <code>convertWebsiteToPdf</code> und <code>convertWebsiteToScreenshot</code> haben keine Job-Zeile zum Abfragen, ein 5xx bedeutet dort also, dass die Übermittlung selbst fehlgeschlagen ist, und wird direkt gemeldet. V2-Endpunkte nutzen ebenfalls kein Job-Polling; ihre asynchronen Abläufe laufen über <code>getPerceiveBatch</code> und <code>getIngestJob</code>.
</div>

---

## Konfiguration

```java
Enconvert client = Enconvert.builder(System.getenv("ENCONVERT_API_KEY"))
        .baseUrl("https://api.enconvert.com")
        .timeout(Duration.ofSeconds(300))
        .build();
```

Drei Konstruktoren stehen als Kurzform bereit: `new Enconvert(apiKey)`, `new Enconvert(apiKey, baseUrl)` und `new Enconvert(apiKey, baseUrl, timeout)`.

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `apiKey` | `String` | erforderlich | Privater API-Key. Ein null-Wert oder leerer Wert wirft eine `IllegalArgumentException`. |
| `baseUrl` | `String` | `https://api.enconvert.com` | Basis-URL der API. Abschließende Schrägstriche werden entfernt. |
| `timeout` | `Duration` | 300 Sekunden | Gilt sowohl als Verbindungs-Timeout als auch als Timeout pro Anfrage. |

Der Key reist im Header `X-API-Key`. Vorsignierte Download-URLs werden ohne ihn abgerufen, da sie bereits signiert sind. Key-Typen behandelt die [Authentifizierung](/de/docs/authentication); Keys erstellst und verwaltest du im [Dashboard](/de/dashboard).

<div class="alert alert-warning">
<strong>Schreibe den API-Key niemals fest in den Code.</strong> Lies ihn aus einer Umgebungsvariablen oder deinem Secret-Manager. Das SDK ist ausschließlich serverseitig: Ein privater Key darf nicht in einem Desktop- oder Mobile-Artefakt ausgeliefert werden, das ein Nutzer entpacken kann.
</div>

---

## Ergebnisform

Jede Konvertierung einer einzelnen Datei oder einer einzelnen URL gibt dasselbe Record zurück:

```java
public record ConversionResult(
        String presignedUrl,
        String objectKey,
        String filename,
        Long fileSize,
        Double conversionTimeSeconds,
        String jobId) {}
```

Die vorsignierte URL ist zeitlich begrenzt. Übergib `saveTo` (oder rufe die URL selbst ab) und speichere die Bytes in deinem eigenen Bucket, wenn sie diese Frist überdauern sollen.

Die anderen Antwort-Records, mit denen du am häufigsten zu tun hast:

| Record | Wichtige Accessoren |
|--------|---------------|
| `JobStatus` | `status()` (`processing`, `success`, `failed`), `presignedUrl()`, `objectKey()`, `error()` |
| `BatchStatus` | `status()`, `total()`, `completed()`, `failed()`, `inProgress()`, `zipDownloadUrl()`, `items()` |
| `PerceiveResult` | `operationId()`, `renderQuality()`, `statusCode()`, `deductions()`, `outputs()`, `structured()`, `cacheHit()`, `warnings()` |
| `V2OutputArtifact` | `url()`, `objectKey()`, `sizeBytes()`, `contentType()`, `expiresIn()` |
| `PerceiveDirectResult` | `content()`, `contentType()`, `filename()`, `renderQuality()`, `contentHash()` |
| `IngestJob` | `jobId()`, `status()`, `pagesProcessed()`, `totalChunks()`, `outputUrl()`, `webhookDelivered()` |
| `Watcher` | `watcherId()`, `status()`, `frequencyMinutes()`, `checksCount()`, `nextCheckAt()`, `lastChangeAt()` |

Felder, deren Struktur durch deine eigene Anfrage bestimmt wird (`structured`, `data`, `trackFields`, Snapshot-`changes`), werden als Gson-`JsonObject` bereitgestellt und unverändert durchgereicht. Zeichenketten-basierte Aufzählungen bleiben `String` und werden nicht zu Java-`enum`-Konstanten, ein neuerer API-Wert bricht also nie die Deserialisierung in einem älteren SDK-Build. `com.enconvert.model.v2.V2Enums` enthält jeden akzeptierten Wert als tippfehlersichere Konstante.

---

## Quelle und Issues

- **Maven Central:** `com.enconvert:enconvert-sdk:0.0.1`
- **GitHub:** [conversionapi/java-sdk](https://github.com/conversionapi/java-sdk)
- **Lizenz:** MIT
- **Weitere Clients:** [alle SDKs](/de/docs/sdks) · [Endpunkt-Referenz](/de/docs/endpoints-overview) · [Preise](/de/pricing)

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in Java mit einer Maven-Abhängigkeit?

Füge `com.enconvert:enconvert-sdk:0.0.1` zu deiner `pom.xml` oder `build.gradle` hinzu, baue einen Client mit `new Enconvert(System.getenv("ENCONVERT_API_KEY"))` und rufe eine typisierte Methode wie `convertUrlToPdf`, `convertImage`, `convertDocument` oder `convertToPdf` auf. Übergib `saveTo` im Options-Builder, um die Ausgabe direkt auf die Festplatte zu schreiben, statt die vorsignierte URL selbst zu verarbeiten.

### Wie konvertiere ich eine URL in Java in ein PDF?

Rufe `client.convertUrlToPdf(url, UrlToPdfOptions.builder().saveTo("page.pdf").build())` auf. Setze `singlePage(false)`, um anhand von `pdfOptions.pageSize` zu paginieren, statt eine einzige fortlaufende Seite zu erzeugen, und übergib `auth`, `cookies` oder `headers` für eine Seite hinter einem Login.

### Wie konvertiere ich DOCX in Java in ein PDF?

Rufe `client.convertDocument(Path.of("report.docx"), ConvertDocumentOptions.builder().saveTo("report.pdf").build())` auf. Das Ausgabeformat ist standardmäßig `pdf`, du setzt `outputFormat` also nur, wenn du etwas anderes willst, zum Beispiel `yaml` aus einer `.json`-Eingabe. Für Formate ohne eigenes Paar, etwa EPUB oder RTF, nimm `convertToPdf`.

### Wie konvertiere ich HEIC in Java in WebP?

Rufe `client.convertImage(Path.of("photo.heic"), ConvertImageOptions.builder("webp").saveTo("photo.webp").build())` auf. Das Eingabeformat ergibt sich aus der Dateiendung, und das Ausgabeformat ist das erforderliche Builder-Argument. `jpeg`, `png`, `svg`, `heic` und `webp` konvertieren alle untereinander, und `pdf` wird zu `jpeg` gerastert. Eine Methode zum Komprimieren an Ort und Stelle gibt es in diesem Client nicht.

### Wie scrape ich aus Java eine Webseite in sauberes Markdown?

Es gibt zwei Wege. `client.convertUrlToMarkdown(url, ...)` liefert GitHub Flavored Markdown mit YAML-Frontmatter als herunterladbare Datei. `client.v2.perceive(url, PerceiveOptions.builder().outputs(List.of("markdown")).build())` liefert denselben Inhalt als agentenfertiges Artefakt mit einem `renderQuality`-Wert, Extraktionsoptionen und Cache-Steuerung. Nimm perceive, wenn ein schlechter Lesevorgang erkennbar sein muss statt still zu bleiben.

### Was ist renderQuality und warum trägt jeder Lesevorgang einen Wert?

`renderQuality` ist ein Wert von 0.0 bis 1.0, der an jedem V2-Render hängt. Ein hoher Wert bedeutet, dass die Seite sauber gerendert wurde; ein niedriger Wert bedeutet, dass etwas dazwischenkam, etwa eine Bot-Challenge, eine Cookie-Wall, ein Login-Bildschirm, eine HTTP-Fehlerseite oder eine leere SPA-Hülle. Der Inhalt wird trotzdem zurückgegeben, mit gefüllten `warnings()` und `deductions()`, sodass deine Pipeline den Lesevorgang verwerfen oder wiederholen kann, statt eine Challenge-Seite so an ein Modell zu geben, als wäre sie der Artikel.

### Wie verwandle ich eine Doku-Website in Java in RAG-fertige Chunks?

Rufe `client.v2.ingest(IngestOptions.builder().mode("sitemap").url("https://docs.example.com").maxPages(100).chunk(new IngestChunkOptions(512, 1)).build())` auf. Der Job ist asynchron, frage also entweder `getIngestJob(jobId)` ab, bis der Status `completed` lautet, und lies `outputUrl()` für das JSONL, oder setze `webhookUrl` und verifiziere die HMAC-Signatur mit dem Secret aus `getWebhookSecret()`. Für lokale Dokumente statt einer Website nimm `ingestFiles`.

### Wie geht das SDK mit Konvertierungen um, die das Proxy-Timeout überdauern?

Vor jeder Anfrage für eine einzelne URL oder einen Datei-Upload erzeugt es eine UUID und sendet sie als `job_id`. Gibt die Anfrage 5xx zurück, fragt es `GET /v1/convert/status/{jobId}` alle 3 Sekunden ab, bis der Job `success` oder `failed` meldet, mit einer Frist von 5 Minuten, nach der es `ApiException(504, "Conversion timed out")` wirft. Batch-Übermittlungen für ganze Websites sind ausgenommen, weil sie keine Job-Zeile zum Abfragen haben.

### Welche Java-Version verlangt das SDK, und was zieht es nach?

Java 17 oder neuer. HTTP läuft über den `java.net.http.HttpClient` des JDK, und Gson ist das einzige Drittanbieter-Artefakt auf dem Classpath. Antworten sind Java-Records, ein modernes `switch` oder Pattern-Matching darüber funktioniert also wie erwartet. Der Client ist threadsicher: Halte eine Instanz und teile sie.
