{"id":50570507,"url":"https://github.com/phax/kaltblut","last_synced_at":"2026-06-04T18:00:48.449Z","repository":{"id":357608631,"uuid":"1235872121","full_name":"phax/kaltblut","owner":"phax","description":"Java library helping you to handle ZUGFeRD invoices","archived":false,"fork":false,"pushed_at":"2026-06-03T10:59:51.000Z","size":368071,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-03T12:23:34.035Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T18:29:53.000Z","updated_at":"2026-06-03T10:59:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/phax/kaltblut","commit_stats":null,"previous_names":["phax/kaltblut"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/phax/kaltblut","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fkaltblut","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fkaltblut/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fkaltblut/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fkaltblut/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phax","download_url":"https://codeload.github.com/phax/kaltblut/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fkaltblut/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33916320,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T18:00:47.778Z","updated_at":"2026-06-04T18:00:48.441Z","avatar_url":"https://github.com/phax.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kaltblut\n\n\u003c!-- ph-badge-start --\u003e\n[![Sonatype Central](https://maven-badges.sml.io/sonatype-central/com.helger.kaltblut/kaltblut-parent-pom/badge.svg)](https://maven-badges.sml.io/sonatype-central/com.helger.kaltblut/kaltblut-parent-pom/)\n[![javadoc](https://javadoc.io/badge2/com.helger.kaltblut/kaltblut-testfiles/javadoc.svg)](https://javadoc.io/doc/com.helger.kaltblut/kaltblut-testfiles)\n\n\u003e If this project saved you some time or made your day a little easier, a star would mean a lot — it helps others find it too.\n\u003c!-- ph-badge-end --\u003e\n\nA Java toolkit for working with **ZUGFeRD / Factur-X** hybrid invoices: detect the flavor of any\nhybrid PDF, extract the embedded XML and supporting attachments, and validate the carrier-side\nspecification rules — including PDF/A-3 conformance via [veraPDF](https://verapdf.org/).\n\nXML-side business rules (cardinalities, EN 16931 rules, code lists ...) are out of scope for this\nproject; use [phive-rules-zugferd](https://github.com/phax/phive-rules) for those.\n\nPer-version requirements analysis used to design this library lives under\n[`docs/requirements/`](docs/requirements/). See\n[`docs/requirements/comparison.md`](docs/requirements/comparison.md) for a cross-version overview\nof every PDF carrier rule from ZUGFeRD 1.0 (2014) through Factur-X 1.08 / ZUGFeRD 2.4 (2025-12-04).\n\n## Supported Versions\n\nThe detection table covers every published release since 2014:\n\n| ZUGFeRD | Factur-X | XMP namespace URI                                              | Embedded XML name           |\n| ------- | -------- | -------------------------------------------------------------- | --------------------------- |\n| 1.0     | n/a      | `urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#`             | `ZUGFeRD-invoice.xml`       |\n| 2.0.1   | n/a      | `urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#`          | `zugferd-invoice.xml`       |\n| 2.1     | 1.0.05   | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml`              |\n| 2.1     | 1.0.05   | `urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#` (legacy) | `zugferd-invoice.xml`       |\n| 2.2     | 1.0.06   | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml` / `xrechnung.xml` |\n| 2.2     | 1.0.06   | `urn:zugferd:pdfa:CrossIndustryDocument:invoice:1p0#` (legacy) | `zugferd-invoice.xml`       |\n| 2.3     | 1.0.07   | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml` / `xrechnung.xml` |\n| 2.3.2   | 1.07.2   | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml` / `xrechnung.xml` |\n| 2.3.3   | 1.07.3   | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml` / `xrechnung.xml` |\n| 2.4     | 1.08     | `urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#`         | `factur-x.xml` / `xrechnung.xml` |\n\n## Why \"Kaltblut\"?\n\n**Kaltblut** is German for the family of heavy draft horse breeds — the strongest and steadiest of the *Zugpferde*.\nThe name is a nod to ZUGFeRD, which German speakers hear as Zugpferd (\"draft horse\").\nKaltblut picks the most workmanlike of them: built to pull heavy loads calmly and reliably, much like this toolkit aims to handle ZUGFeRD invoices.\n\nAs a bonus, *kaltblütig* in everyday German also means \"cool-headed\" — a useful trait for any library dealing with tax-relevant invoice processing\n\n## What Kaltblut Does (and Does Not Do)\n\n| Tier | Concern                                                                                     | Status      |\n| ---- | ------------------------------------------------------------------------------------------- | ----------- |\n| 1    | **Detection \u0026 metadata**: flavor, profile, XMP fields, embedded-file name, `/AFRelationship` | implemented |\n| 2    | **Extraction**: invoice XML, named attachments, full attachment list                        | implemented |\n| 3    | **Validation**: BR-HYBRID-* business rules, PDF/A-3 (via veraPDF SPI)                       | implemented |\n| 4    | Creation / embedding XML to produce hybrid PDFs                                             | not in scope |\n| —    | XML business-rules validation (EN 16931, KoSIT XRechnung)                                   | use [phive-rules-zugferd](https://github.com/phax/phive-rules) |\n\n## Project Layout\n\nThis is a multi-module Maven project:\n\n- `kaltblut-core` — the library. Source abstraction, model, inspector, extractor, validator.\n  Classes live under `com.helger.kaltblut.core.*`.\n- `kaltblut-testfiles` — shared test fixtures (sample PDFs + classpath-resource locator).\n- `kaltblut-verapdf` — PDF/A-3 validation adapter that wires veraPDF to the\n  `IPdfA3ValidatorSPI` SPI. Optional; pull it in only if you need PDF/A-3 conformance checks.\n- `kaltblut-cli` — the command-line client (picocli). Builds a standalone fat JAR.\n\n## Key Library Concepts\n\n### `IHybridSource` — the input abstraction\n\nAll public entry points take a source. Use one of the `HybridSource` factories:\n\n```java\nimport com.helger.kaltblut.core.source.HybridSource;\nimport com.helger.kaltblut.core.source.IHybridSource;\n\nIHybridSource s1 = HybridSource.fromFile (new File (\"invoice.pdf\"));         // lazy + cached\nIHybridSource s2 = HybridSource.fromPath (Path.of (\"invoice.pdf\"));          // lazy + cached\nIHybridSource s3 = HybridSource.fromBytes (aPdfBytes);                       // wraps array\nIHybridSource s4 = HybridSource.fromByteBuffer (aBuffer);                    // copies\nIHybridSource s5 = HybridSource.fromUrl (new URL (\"https://example.com/invoice.pdf\"));      // http/https only, with timeouts\nIHybridSource s6 = HybridSource.fromInputStream (aIS);                       // reads now, closes\nIHybridSource s7 = HybridSource.fromClasspath (\"samples/invoice.pdf\");       // resource path\n```\n\n`IHybridSource` is byte-array-centric: the contract is `byte[] getBytes() throws IOException`,\nplus `long getSize()` and `String getName()` as diagnostic hints. PDFBox 3 needs random access\nand every Kaltblut operation eventually needs the complete PDF in memory, so distinguishing\nsingle-read from multi-read inputs added API surface without value. Implementations may read\nlazily on first call and cache the result; callers must not mutate the returned array.\n\n`HybridSource.fromUrl` only accepts `http` and `https` URLs and applies sensible connect / read\ntimeouts (defaults 10 s / 60 s); other schemes (`file:`, `jar:`, `ftp:`, ...) are refused to\nprevent accidental SSRF / local-file-read when forwarding caller-supplied URLs.\n\n### `HybridLimits` — byte / count ceilings\n\nEvery Tier-1/2/3 entry point accepts an optional `HybridLimits` (defaulting to\n`HybridLimits.DEFAULTS`) that caps:\n\n- the input PDF size (default 64 MiB),\n- per-attachment inflated size (default 32 MiB),\n- aggregate attachment size (default 128 MiB),\n- attachment count (default 100).\n\nUse `HybridLimits.UNLIMITED` to disable, or build a custom instance with the immutable\n`withMaxPdfBytes(...)` / `withMaxAttachmentBytes(...)` / ... witherers. Reading past a limit\nthrows `IOException` rather than letting the JVM OOM.\n\n### Model\n\nThe model classes in `com.helger.kaltblut.core.model` are immutable value objects:\n\n- `EZugferdFlavor` — namespace-URI fingerprint of the spec generation.\n- `EZugferdProfile` — `MINIMUM`, `BASIC_WL`, `BASIC`, `COMFORT`, `EN_16931`, `EXTENDED`, `XRECHNUNG`.\n- `EAFRelationship` — `Data`, `Source`, `Alternative`, `Supplement`, `Unspecified`.\n- `EZugferdCountry` — `DE`, `FR`, `OTHER` (drives country-specific BR-HYBRID rules).\n- `HybridMetadata` — single snapshot of XMP fields + `/AF` data.\n- `HybridAttachment` — name, MIME type, AFRelationship, ModDate, bytes, invoice-XML flag.\n\n## Usage\n\n### Tier 1: detection\n\n```java\nimport com.helger.kaltblut.core.inspect.HybridInspector;\nimport com.helger.kaltblut.core.model.EZugferdFlavor;\nimport com.helger.kaltblut.core.model.HybridMetadata;\n\nIHybridSource aSource = HybridSource.fromFile (new File (\"invoice.pdf\"));\n\nif (HybridInspector.isHybridInvoice (aSource))\n{\n  EZugferdFlavor eFlavor = HybridInspector.detectFlavor (aSource);\n  HybridMetadata aMeta = HybridInspector.readMetadata (aSource);\n  System.out.println (\"Flavor:        \" + aMeta.getFlavor ());\n  System.out.println (\"Profile:       \" + aMeta.getProfile ());\n  System.out.println (\"Embedded file: \" + aMeta.getEmbeddedFileName ());\n  System.out.println (\"AFRelationship: \" + aMeta.getAFRelationship ());\n}\n```\n\n### Tier 2: extraction\n\n```java\nimport com.helger.kaltblut.core.extract.HybridExtractor;\nimport com.helger.kaltblut.core.model.HybridAttachment;\n\nbyte [] aXmlBytes = HybridExtractor.extractInvoiceXml (aSource);\nList \u003cHybridAttachment\u003e aAttachments = HybridExtractor.listAttachments (aSource);\nbyte [] aExcel = HybridExtractor.extractAttachment (aSource, \"list_of_measurement.xlsx\");\n```\n\n**Security note:** the bytes returned by `extractInvoiceXml` / `extractAttachment` come from a\npotentially untrusted PDF. If you parse the XML yourself, configure your XML processor to disable\nexternal entities, DTDs, and XInclude (i.e. `FEATURE_SECURE_PROCESSING=true` plus\n`disallow-doctype-decl=true`), or use a library — such as `phive-rules-zugferd` — that does so\nby default. Otherwise a malicious invoice can XXE-read local files or trigger SSRF.\n\n### Tier 3: validation\n\n```java\nimport com.helger.kaltblut.core.model.EZugferdCountry;\nimport com.helger.kaltblut.core.validate.HybridFinding;\nimport com.helger.kaltblut.core.validate.HybridValidator;\nimport com.helger.kaltblut.core.validate.HybridValidationLayer;\nimport com.helger.kaltblut.core.validate.HybridValidationResult;\n\nHybridValidator aValidator = new HybridValidator ();\naValidator.getSettings ()\n          .setCountry (EZugferdCountry.DE)\n          .setCheckPdfA3 (true)\n          .setApplyDePdfADowngrade (true);\n\nHybridValidationResult aResult = aValidator.validate (aSource);\n\n// Per-layer reporting: one BR_HYBRID layer + (when enabled) one PDF_A3 layer, each\n// carrying its own findings and wall-clock duration.\nfor (HybridValidationLayer aLayer : aResult.getAllLayers ())\n{\n  System.out.println (aLayer.getDisplayName () + \" - \" + aLayer.getDuration ().toMillis () + \"ms\");\n  for (HybridFinding aF : aLayer.getAllFindings ())\n    System.out.println (\"  \" + aF);\n}\n\n// Aggregate predicates still work across all layers.\nif (!aResult.isValid ())\n  System.err.println (\"Document considered invalid\");\n```\n\nPDF/A-3 validation runs via the `IPdfA3ValidatorSPI` SPI. Add `kaltblut-verapdf` to the classpath\nto enable veraPDF; without it `validate()` records a single `INFORMATION` finding noting that\nPDF/A-3 conformance was not checked.\n\n### Command line\n\nBuild the standalone fat JAR and run it:\n\n```shell\nmvn clean package\njava -jar kaltblut-cli/target/kaltblut-cli-full.jar [subcommand] [options] \u003cfiles...\u003e\n```\n\nSubcommands:\n\n| Subcommand    | Description                                                                          |\n| ------------- | ------------------------------------------------------------------------------------ |\n| `inspect`     | Print flavor, profile, XMP fields, embedded-file name, and `/AFRelationship`.        |\n| `extract`     | Write the embedded invoice XML to disk.                                              |\n| `attachments` | List all embedded files (invoice XML + supporting documents).                        |\n| `validate`    | Run BR-HYBRID-* business rules and PDF/A-3 validation. Exit code 0 if no ERROR findings. |\n\nCommon options:\n\n| Option                                | Subcommand | Description                                                            | Default     |\n| ------------------------------------- | ---------- | ---------------------------------------------------------------------- | ----------- |\n| `-o`, `--output-dir`                  | `extract`  | Directory to write XML files to                                        | `.`         |\n| `-s`, `--suffix`                      | `extract`  | Output filename suffix                                                 | `-invoice`  |\n| `-c`, `--country`                     | `validate` | `DE`, `FR`, or `OTHER` — drives country-specific rules                 | `OTHER`     |\n| `--no-pdfa`                           | `validate` | Skip PDF/A-3 validation via the SPI                                    | off         |\n| `--no-de-pdfa-downgrade`              | `validate` | Disable the BR-FX-DE-03 downgrade for DE↔DE invoices                   | off         |\n| `-h`, `--help`                        | all        | Show help                                                              |             |\n| `-V`, `--version`                     | all        | Show version                                                           |             |\n\nExamples:\n\n```shell\n# Detect the flavor of one or more PDFs\njava -jar kaltblut-cli-full.jar inspect invoice.pdf another-invoice.pdf\n\n# Extract the invoice XML to /tmp/out/\njava -jar kaltblut-cli-full.jar extract -o /tmp/out invoice.pdf\n\n# List all embedded files in a PDF\njava -jar kaltblut-cli-full.jar attachments invoice.pdf\n\n# Validate a DE↔DE invoice (PDF/A-3 errors downgraded per BR-FX-DE-03)\njava -jar kaltblut-cli-full.jar validate -c DE invoice.pdf\n\n# Validate without PDF/A-3 (fast path; only the BR-HYBRID-* rules run)\njava -jar kaltblut-cli-full.jar validate --no-pdfa invoice.pdf\n```\n\n## Building\n\nRequires Java 17+ and Maven.\n\n```shell\nmvn clean package\n```\n\nThe build produces (replacing `x.y.z` with the effective version):\n\n- `kaltblut-core/target/kaltblut-core-x.y.z-SNAPSHOT.jar` — core library JAR.\n- `kaltblut-verapdf/target/kaltblut-verapdf-x.y.z-SNAPSHOT.jar` — veraPDF adapter JAR.\n- `kaltblut-cli/target/kaltblut-cli-x.y.z-SNAPSHOT.jar` — CLI library JAR.\n- `kaltblut-cli/target/kaltblut-cli-full.jar` — standalone executable fat JAR (all dependencies bundled).\n\n## Maven Coordinates\n\n```xml\n\u003c!-- Core library: detection + extraction + BR-HYBRID validation --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.kaltblut\u003c/groupId\u003e\n  \u003cartifactId\u003ekaltblut-core\u003c/artifactId\u003e\n  \u003cversion\u003ex.y.z\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Optional: veraPDF-backed PDF/A-3 validation --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.kaltblut\u003c/groupId\u003e\n  \u003cartifactId\u003ekaltblut-verapdf\u003c/artifactId\u003e\n  \u003cversion\u003ex.y.z\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Extending\n\nTo plug in a different PDF/A-3 validator (or none at all), implement\n`com.helger.kaltblut.core.validate.IPdfA3ValidatorSPI` and register the class via\n`META-INF/services/com.helger.kaltblut.core.validate.IPdfA3ValidatorSPI`. The validator is\ndiscovered via `ServiceLoader`; only the first implementation found is used.\n\n## License\n\nApache License, Version 2.0.\n\n## News and Noteworthy\n\nv0.9.1 - 2026-05-13\n* Validation: the result of `HybridValidator.validate` is now structured as a list of\n  `HybridValidationLayer`s (`BR_HYBRID` + optional `PDF_A3`, identified by\n  `EHybridValidationLayerKind`) instead of a flat finding list. Each layer carries its own findings\n  and wall-clock `Duration`. Aggregate predicates on `HybridValidationResult` continue to work\n  across all layers.\n* **Breaking**: `ValidationResult` renamed to `HybridValidationResult`, and the new\n  per-layer container is `HybridValidationLayer`.\n* **Breaking**: `EHybridSeverity.FATAL` renamed to `ERROR`. Predicate methods follow:\n  `isFatal()` → `isError()`, `hasFatal()` → `hasError()`, `hasFatalRule()` → `hasErrorRule()`.\n* `EHybridSeverity` entries now carry the equivalent ph-commons `EErrorLevel` via\n  `getErrorLevel()`, so consumers mapping findings into ph-commons error infrastructure no\n  longer need a translation table.\n* `EZugferdCountry` now implements `IHasID\u003cString\u003e` with `getID()` and the static\n  `getFromIDOrNull(String)` factory, matching the style of the other ph-commons-based enums.\n* CLI `validate` subcommand prints one line per layer with its kind, finding count, and\n  duration, then the layer's findings indented underneath.\n\nv0.9.0 - 2026-05-13\n* Detection: recognises all five XMP extension-schema namespaces seen across ZUGFeRD 1.0, 2.0.1, 2.1, 2.2, 2.3, 2.3.2, 2.3.3 and 2.4.\n* Extraction: invoice XML, named attachments, full attachment list including Modification Date and MIME type.\n* Validation: BR-HYBRID-01 through BR-HYBRID-15 (and the BR-HYBRID-DE-*/-FR-* country\n  variants) plus PDF/A-3 conformance via the `IPdfA3ValidatorSPI` SPI implemented by\n  `kaltblut-verapdf` using veraPDF (`-jakarta` artifact line, JAXB 4.x only).\n* Command-line client with subcommands `inspect`, `extract`, `attachments`, `validate`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fkaltblut","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphax%2Fkaltblut","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fkaltblut/lists"}