PDF/A-1b vs PDF/A-3 for Format Normalization: Choosing a Stable Preservation Target

When a digitization pipeline normalizes born-digital or scanned documents to a PDF/A conformance level, the choice between PDF/A-1b and PDF/A-3b is not a cosmetic version bump — it decides whether the resulting archival master is a self-contained, cheaply identifiable object or a container that can smuggle arbitrary, unmanaged payloads past your registry. This guide sits under Format Registry Integration and treats the decision as a preservation-risk evaluation rather than a feature comparison: both levels share the same “level b” visual-fidelity floor, but they differ sharply in what they allow inside the file, and that difference propagates all the way downstream to how Preservation Format Identification tools reason about the object and what a PREMIS migration event can honestly assert about it.

The Shared Floor and Where It Diverges

PDF/A-1b and PDF/A-3b both certify conformance level b (“basic”): the guarantee is that the visual appearance of the page is reliably reproducible, with fonts embedded, colour unambiguously specified, and no reliance on external resources for rendering. Neither level b variant makes the stronger level a (“accessible”) promise of a tagged logical structure and Unicode-mapped text. So on the axis most people first ask about — “will it look the same in 40 years?” — the two are equivalent. The decision is driven entirely by the axes below.

  • Base PDF version. PDF/A-1 is bound to PDF 1.4 (the Acrobat 5 feature set). PDF/A-3 is bound to PDF 1.7 (ISO 32000-1). Every capability PDF gained between 1.4 and 1.7 — transparency groups, blend modes, optional-content layers, JPEG 2000 image XObjects, richer digital-signature dictionaries — is unavailable in PDF/A-1 and available (within constraints) in PDF/A-3.
  • Embedded files. This is the decisive preservation difference. PDF/A-1 forbids embedded files entirely. PDF/A-2 relaxed this to permit embedding other valid PDF/A files. PDF/A-3 removed even that restriction: it permits embedding files of arbitrary format — a spreadsheet, a CAD drawing, a proprietary binary, anything — with no requirement that the embedded payload itself be preservation-grade.
  • Transparency and layers. PDF/A-1 flattens the world: no live transparency, no optional-content groups (layers). PDF/A-3 permits transparency and layers, which faithfully preserves documents that genuinely use them but enlarges the rendering surface a validator and a future renderer must implement correctly.
  • Image compression. PDF/A-1 cannot carry JPEG 2000 (JPXDecode) image data; PDF/A-3 can. For a pipeline that already normalizes raster masters to JPEG 2000 elsewhere, PDF/A-3 lets that codec survive inside the PDF wrapper; PDF/A-1 forces transcoding to a 1.4-era filter.

The font rule is identical in spirit across both: all fonts must be fully embedded with legal embedding rights, and every glyph the content stream uses must be present. That rule is what makes either level a defensible master; it is not a differentiator.

Why “Arbitrary Embedded Files” Is a Preservation Risk, Not a Feature

PDF/A-3’s headline capability — carry the source alongside the archival rendering — is exactly what makes it dangerous as a default normalization target. The original use case is legitimate: an invoice PDF/A-3 carrying the machine-readable XML it was rendered from (the ZUGFeRD/Factur-X pattern). But from a repository’s point of view, an arbitrary embedded file is an unidentified, unvalidated object hiding inside an object you have certified as preservation-grade.

Three concrete hazards follow, and the diagram below maps each one:

  1. Identification blindness. Your format-identification pass runs DROID or Siegfried against the outer file, gets a clean fmt/480 (PDF/A-3b) PUID, and records it. The embedded spreadsheet is never surfaced as a distinct object, never assigned its own PUID, and never enters your preservation-planning risk register. You have certified a container without inventorying its contents.
  2. Fixity ambiguity. The SHA-256 you record covers the whole container. When the outer PDF/A is later migrated or re-saved, the embedded payload’s own integrity is not independently verifiable unless you extracted and hashed it as a separate object at ingest.
  3. Preservation-action dishonesty. A PREMIS migration event that says “normalized to PDF/A-3b” is technically true and materially incomplete if that file wraps a proprietary binary with a five-year support horizon. The master looks stable; part of its payload is not.
PDF/A-1b versus PDF/A-3b: what each level permits inside the container A branching diagram. A shared base box states that both PDF/A-1b and PDF/A-3b certify conformance level b: reproducible visual appearance with fully embedded fonts. The left branch, PDF/A-1b, forbids embedded files and yields one self-contained master that identification tools resolve to a single PUID and that fixity and PREMIS cover completely. The right branch, PDF/A-3b, permits embedded files of arbitrary format; the outer container identifies to a single PUID, but the embedded payload is a hidden object that is not identified, not independently fixity-checked, and not entered into preservation planning. Shared floor: conformance level b reproducible appearance · fonts fully embedded PDF 1.4 PDF 1.7 PDF/A-1b embedded files FORBIDDEN One self-contained master nothing hidden inside identify → 1 PUID fixity covers the whole object PREMIS event is complete PDF/A-3b arbitrary embedded files OK Container + hidden payload e.g. proprietary binary inside identify → 1 PUID (outer only) payload NOT identified NOT fixity-checked alone absent from preservation plan Same visual guarantee — different attack surface for identification & planning choose A-1b unless source and derivative genuinely must travel together

Both levels hit the same visual-fidelity floor; only PDF/A-3b lets an unidentified payload ride inside a certified master.

Comparison Matrix

Dimension PDF/A-1b PDF/A-3b Preservation consequence
Standard part ISO 19005-1 (2005) ISO 19005-3 (2012) Both current; -1 is the most widely supported
Base PDF version PDF 1.4 PDF 1.7 (ISO 32000-1) -3 inherits a larger, less battle-tested feature surface
Conformance basis Level b (visual) Level b (visual) Identical rendering guarantee
Embedded files Forbidden Arbitrary format permitted -3 hides unidentified objects inside a certified master
Transparency / blend modes Not allowed (flattened) Allowed -1 removes a whole class of renderer bugs
Optional-content layers Not allowed Allowed -1 yields a single deterministic view
JPEG 2000 (JPXDecode) Not allowed Allowed -3 lets an existing JP2 stream survive un-transcoded
Font embedding All fonts embedded, subset OK All fonts embedded, subset OK Non-differentiating floor requirement
Typical PRONOM PUID fmt/95 fmt/480 Identification resolves the outer container only
veraPDF flavour string 1b 3b Drives the validation profile you must assert
Best fit Simplest stable single-file masters Source + derivative that must travel together See verdict below

Validating the Conformance Level with veraPDF

Whatever you choose, the normalization step is only trustworthy if you machine-verify the claimed conformance level rather than trusting the producing tool. veraPDF is the reference PDF/A validator; the industry-standard way to wire it into a pipeline is to invoke its CLI in --format json mode and parse the structured result. The function below runs veraPDF as a subprocess, extracts the detected flavour and the pass/fail verdict, and emits a structured log line for the preservation record. It is deliberately defensive: veraPDF exit codes distinguish “ran, file non-compliant” from “failed to run”, and the two must never be conflated.

python
from __future__ import annotations

import json
import logging
import subprocess
from dataclasses import dataclass

logger = logging.getLogger("archival.format.pdfa")


@dataclass(frozen=True)
class PdfaVerdict:
    """Structured outcome of a single veraPDF validation run."""

    file_path: str
    flavour: str          # e.g. "1b", "3b", or "0" when auto-detection is inconclusive
    is_compliant: bool
    failed_checks: int


def validate_pdfa(file_path: str, verapdf_bin: str = "verapdf") -> PdfaVerdict:
    """Validate a PDF against its detected PDF/A flavour and log the outcome.

    Raises RuntimeError if veraPDF cannot execute or returns unparseable output,
    which is distinct from a file that runs but is non-compliant.
    """
    cmd = [verapdf_bin, "--flavour", "0", "--format", "json", file_path]
    try:
        completed = subprocess.run(
            cmd, capture_output=True, text=True, timeout=300, check=False
        )
    except (OSError, subprocess.TimeoutExpired) as exc:
        logger.error(
            "verapdf_invocation_failed",
            extra={"file": file_path, "error": str(exc)},
        )
        raise RuntimeError(f"veraPDF could not validate {file_path}") from exc

    try:
        report = json.loads(completed.stdout)
        job = report["report"]["jobs"][0]
        validation = job["validationResult"][0]
        flavour = validation["profileName"].split()[1].lower()  # "PDF/A-1B ..." -> "1b"
        is_compliant = bool(validation["compliant"])
        failed = int(validation["details"]["failedChecks"])
    except (json.JSONDecodeError, KeyError, IndexError, ValueError) as exc:
        logger.error(
            "verapdf_output_unparseable",
            extra={"file": file_path, "stderr": completed.stderr[:500]},
        )
        raise RuntimeError(f"Unparseable veraPDF output for {file_path}") from exc

    logger.info(
        "pdfa_validated",
        extra={
            "file": file_path,
            "flavour": flavour,
            "compliant": is_compliant,
            "failed_checks": failed,
        },
    )
    return PdfaVerdict(file_path, flavour, is_compliant, failed)

Asserting the expected flavour is what turns validation into a policy gate. A file that validates cleanly as 3b when your normalization profile promised 1b is a policy violation even though veraPDF reports “compliant” — the producing tool silently targeted the wrong level.

python
def enforce_target_flavour(file_path: str, expected: str = "1b") -> None:
    """Fail the ingest step when the master does not match the mandated level."""
    verdict = validate_pdfa(file_path)
    if not verdict.is_compliant or verdict.flavour != expected:
        logger.error(
            "pdfa_policy_violation",
            extra={
                "file": file_path,
                "expected_flavour": expected,
                "actual_flavour": verdict.flavour,
                "compliant": verdict.is_compliant,
            },
        )
        raise ValueError(
            f"{file_path}: expected PDF/A-{expected}, got "
            f"{verdict.flavour} (compliant={verdict.is_compliant})"
        )
    logger.info("pdfa_policy_ok", extra={"file": file_path, "flavour": expected})

Surfacing Embedded Files Before They Escape the Inventory

If policy permits PDF/A-3 for a specific collection, the non-negotiable mitigation is to enumerate every embedded file at ingest, hash each one independently, and register it as a distinct object so nothing enters the archive uninventoried. The standard library reads the embedded-file name tree without a heavyweight PDF dependency for the common case, but a production pipeline should parse the /EmbeddedFiles name tree with a real PDF library; the routine below uses pikepdf (a qpdf binding) to extract each payload and record a per-file SHA-256.

python
import hashlib

import pikepdf

logger = logging.getLogger("archival.format.pdfa.embedded")


def inventory_embedded_files(file_path: str) -> list[dict[str, object]]:
    """List and hash every file embedded in a PDF/A-3 container.

    Returns one record per embedded object so each can be identified and
    entered into preservation planning as a first-class object.
    """
    records: list[dict[str, object]] = []
    with pikepdf.open(file_path) as pdf:
        attachments = pdf.attachments
        for name in attachments:
            spec = attachments[name]
            payload: bytes = spec.get_file().read_bytes()
            digest = hashlib.sha256(payload).hexdigest()
            record = {
                "container": file_path,
                "embedded_name": str(name),
                "size_bytes": len(payload),
                "sha256": digest,
            }
            records.append(record)
            logger.info("embedded_file_found", extra=record)

    if not records:
        logger.info("no_embedded_files", extra={"file": file_path})
    return records

Feeding each extracted payload back through format identification — the same DROID/Siegfried pass described in Preservation Format Identification — is what closes the identification-blindness gap. Pair that with a locally cached registry, kept current via syncing local PRONOM format registry snapshots, so every embedded PUID resolves offline and deterministically.

Downstream Identification Impact

The identification story is the quiet reason to prefer PDF/A-1b. A PDF/A-1b master is a leaf: a single byte stream, a single PUID (typically fmt/95), a single fixity value, and a PREMIS migration event whose scope statement is exhaustive. A PDF/A-3 master is a tree whose root identifies cleanly while its branches are invisible to a naive identification pass. Your registry integration must therefore be recursive for PDF/A-3 — extract, identify, and register embedded payloads — or your inventory silently understates what you hold. When you record the normalization outcome as a PREMIS event, the honest object relationship for a PDF/A-3 container is a hasComponent/isComponentOf structure linking the outer object to each embedded object, not a single flat record; mapping that relationship correctly is the province of PREMIS Metadata Mapping.

Verdict

Default to PDF/A-1b for the simplest stable masters. For the overwhelming majority of digitized documents — reports, correspondence, monographs, forms — PDF/A-1b delivers the identical visual guarantee with the smallest feature surface, no embedded-object blind spots, the widest tool support, and the most honest one-to-one identification and fixity story. Its PDF 1.4 base is the most thoroughly implemented target in every renderer and validator you will encounter.

Reach for PDF/A-3 only when the source and its derivative genuinely must travel together as one object — the Factur-X/ZUGFeRD invoice pattern, or a rendered report that must carry its authoritative machine-readable source. Even then, treat the embedded-file capability as a managed liability: extract and independently identify, hash, and register every payload at ingest; assert the 3b flavour explicitly with veraPDF; and reject any embedded object whose own format is not itself preservation-grade. If you cannot commit to that recursive discipline, PDF/A-3 is the wrong target and PDF/A-1b is the safe one.

Frequently Asked Questions

Is PDF/A-3b more “advanced” and therefore a better preservation target than PDF/A-1b?

No — newer is not safer here. PDF/A-3b’s additional capabilities (a PDF 1.7 base, transparency, layers, JPEG 2000, and above all arbitrary embedded files) enlarge the surface a future renderer and a validator must implement correctly, and the embedded-file allowance lets unidentified objects hide inside a certified master. For a plain document with no companion source to carry, PDF/A-1b gives the same visual-fidelity guarantee with fewer moving parts, which is precisely what long-term preservation rewards.

Why are the arbitrary embedded files in PDF/A-3 considered a risk?

Because an embedded file can be any format, including proprietary binaries with short support horizons, and your outer identification pass sees only the container’s PUID. Unless you extract, identify, hash, and register each embedded object at ingest, it never enters your format-risk register, its fixity is not independently verifiable, and any PREMIS migration event that claims the master is preservation-grade is materially incomplete. The capability is safe only under a recursive inventory discipline.

How do I confirm a normalized file actually is PDF/A-1b and not some other level?

Validate it with veraPDF in auto-detect mode and assert the flavour string it returns. veraPDF reports both a compliance verdict and the detected profile (for example PDF/A-1B); a file that validates as 3b when your profile promised 1b is a policy violation even though it is “compliant”. The enforce_target_flavour gate above rejects any master whose detected flavour differs from the mandated level, so a producing tool cannot silently target the wrong one.

Can I convert an existing PDF/A-3 master down to PDF/A-1b?

Only with data loss you must record. Down-converting means flattening transparency and layers, transcoding any JPEG 2000 streams to a PDF 1.4-compatible filter, and — critically — removing all embedded files. Extract and separately preserve every embedded payload first, then log the conversion as a PREMIS migration event that names what was removed. If the embedded source was the reason the object existed as PDF/A-3, down-converting defeats the purpose and the two objects should be preserved as an explicit relationship instead.