Connector Release 📅 November 12, 2026 ⏱ 10 min read

ACID Lakehouse Streaming, Parquet Storage & Dynamic Schema Evolution: Announcing the Apache Iceberg Output Connector

OpenCrawling introduces oc-iceberg-output-connector: an enterprise-grade lakehouse output connector that streams crawled documents into Apache Iceberg (v1.11.0) tables with columnar Apache Parquet storage. Featuring multi-catalog support (REST, Hive, Hadoop, Glue, JDBC), canonical schema management, Zero-Trust OIS ACL security enforcement, runtime dynamic schema evolution for custom entities, and ACID snapshot commits over S3FileIO, OpenCrawling bridges enterprise repositories directly to modern lakehouse analytics.

Decoupled Ingestion & ACID Lakehouse Streaming on Apache Iceberg
17 Enterprise Repositories
→
Kafka (opencrawling-documents)
→
IcebergStoreWriterConsumer
→
IcebergTableManager (Dynamic Evolution)
→
Parquet Columnar Files (MinIO / S3)
→
Iceberg REST Catalog (ACID Commits)

1. Bridging Enterprise Repositories Directly to the Lakehouse

In modern enterprise data platforms, data architectures often face a divide: enterprise content management (ECM) repositories, document management systems, and workflow engines live in isolated operational silos, while analytical and machine learning workloads thrive on open data lakehouse table formats like Apache Iceberg.

Traditional search and vector databases (such as Elasticsearch, OpenSearch, Solr, pgvector, or Milvus) are unmatched for low-latency top-k semantic search and hybrid kNN retrieval. However, enterprises increasingly require long-term compliance archiving, petabyte-scale historical reporting, multi-engine federated SQL querying (via DuckDB, Trino, and Apache Spark), and immutable snapshot time travel.

With the release of oc-iceberg-output-connector, OpenCrawling becomes the first enterprise ingestion framework capable of streaming unstructured documents, rich metadata attributes, extracted text, and normalized Open Ingestion Standard (OIS) Zero-Trust Access Control Lists (ACLs) directly into Apache Iceberg Parquet tables with transactional snapshot isolation.

🧊 Multi-Catalog & S3 Storage

Pluggable Catalogs: Works out of the box with the standard Iceberg REST Catalog, Hive Metastore, Hadoop filesystem, AWS Glue, and JDBC backends, backed by high-throughput S3FileIO for AWS S3, MinIO, and Apache Ozone.

âš¡ Dynamic Schema Evolution

Zero-Downtime Column Additions: Newly discovered business entities (e.g., vendor, contract_id, invoice_amount) dynamically evolve the table schema at runtime without table locks or data rewrites.

2. Direct Batching vs. Decoupled Kafka Streaming

The Iceberg Output Connector provides architectural flexibility across both standalone and horizontally scaled deployments:

3. Pluggable Enterprise Catalogs via IcebergCatalogFactory

Rather than hardcoding a specific catalog backend, IcebergCatalogFactory instantiates the appropriate Iceberg Catalog based on the spring.opencrawling.output.iceberg.catalog-type property:

4. Canonical Lakehouse Schema & Zero-Trust OIS ACL Enforcement

On startup, IcebergTableManager provisions the target Iceberg namespace and table with the strongly typed CANONICAL_SCHEMA:

// Canonical OpenCrawling Lakehouse Schema for Apache Iceberg
Types.NestedField.required(1,  "id",                   Types.StringType.get(), "Unique record UUID"),
Types.NestedField.required(2,  "document_id",          Types.StringType.get(), "Canonical document ID"),
Types.NestedField.optional(3,  "source_type",          Types.StringType.get(), "Repository connector type"),
Types.NestedField.optional(4,  "document_uri",         Types.StringType.get(), "Original repository URI"),
Types.NestedField.optional(5,  "title",                Types.StringType.get(), "Document title or filename"),
Types.NestedField.optional(6,  "mime_type",            Types.StringType.get(), "MIME content type"),
Types.NestedField.optional(7,  "content_length",       Types.LongType.get(),   "Binary length in bytes"),
Types.NestedField.optional(8,  "sha256_checksum",      Types.StringType.get(), "SHA-256 binary checksum"),
Types.NestedField.optional(9,  "claim_check_uri",      Types.StringType.get(), "Claim-Check object storage URI"),
Types.NestedField.optional(10, "extracted_text",       Types.StringType.get(), "Extracted plain text"),
Types.NestedField.optional(11, "extracted_entities",   Types.MapType.ofOptional(12, 13, Types.StringType.get(), Types.StringType.get())),
Types.NestedField.optional(14, "custom_metadata",      Types.MapType.ofOptional(15, 16, Types.StringType.get(), Types.StringType.get())),
Types.NestedField.optional(17, "security_owner",       Types.StringType.get(), "Primary security owner"),
Types.NestedField.optional(18, "security_inheritance", Types.BooleanType.get(),"Inheritance flag"),
Types.NestedField.optional(19, "allowed_principals",   Types.ListType.ofOptional(20, Types.StringType.get()), "Zero-Trust ACL Allow"),
Types.NestedField.optional(21, "denied_principals",    Types.ListType.ofOptional(22, Types.StringType.get()), "Zero-Trust ACL Deny"),
Types.NestedField.required(23, "action",               Types.StringType.get(), "OIS Action: UPSERT or DELETE"),
Types.NestedField.required(24, "crawled_at",           Types.TimestampType.withoutZone(), "Crawl timestamp UTC")
🔒

End-to-End Governance: By persisting allowed_principals and denied_principals directly inside the Parquet schema, downstream query engines (such as Trino or Spark) can enforce row-level security policies directly on the lakehouse tables!

5. Dynamic Runtime Schema Evolution

Enterprise documents often carry bespoke business descriptors that cannot be anticipated at design time. When contracts, invoices, or claims are crawled, Apache Tika and Spring AI extract custom entities (such as entity.vendor, entity.amount, or entity.contract_id).

When spring.opencrawling.output.iceberg.auto-schema-evolution=true is enabled, IcebergTableManager introspects incoming entity attributes. If a column is missing from the active table schema, it commits an Iceberg schema update via:

// Dynamic runtime schema evolution without table locks or data rewrites
UpdateSchema updateSchema = table.updateSchema();
for (String entityKey : entities.keySet()) {
    String colName = IcebergTableManager.sanitizeIdentifier(entityKey);
    if (currentSchema.findField(colName) == null) {
        updateSchema.addColumn(colName, Types.StringType.get(), "Dynamically extracted entity: " + entityKey);
    }
}
updateSchema.commit();
table.refresh();

Because Apache Iceberg tracks schema versions in metadata rather than rewriting underlying data files, this evolution occurs instantly with zero ingestion downtime.

6. Partition Transforms & Columnar Storage

To prevent the "small files problem" and optimize query pruning, the connector supports configurable partition strategies:

Data files are formatted as pure Apache Parquet compressed with Zstandard (zstd) or Snappy, delivering up to 70% storage savings compared to uncompressed raw JSON while enabling vectorized reads across analytical engines.

7. Configuration Reference

All properties bind cleanly under spring.opencrawling.output.iceberg.*:

Property Default Description
spring.opencrawling.output.type pgvector Set to iceberg to activate the Iceberg Output Connector
spring.opencrawling.output.iceberg.catalog-type in-memory Catalog backend: rest, hive, hadoop, glue, jdbc, or in-memory
spring.opencrawling.output.iceberg.catalog-uri (empty) Remote catalog URI (e.g. http://localhost:8181 for REST)
spring.opencrawling.output.iceberg.warehouse tmp/iceberg-warehouse Root warehouse location (e.g. s3://warehouse)
spring.opencrawling.output.iceberg.namespace opencrawling_ecm Iceberg namespace / database name
spring.opencrawling.output.iceberg.table-name enterprise_documents Target table name under UNIFIED strategy
spring.opencrawling.output.iceberg.table-strategy UNIFIED Routing strategy: UNIFIED or PER_DOCUMENT_TYPE
spring.opencrawling.output.iceberg.auto-create-table true Automatically provisions namespace and table on startup if missing
spring.opencrawling.output.iceberg.auto-schema-evolution true Automatically evolves table schema when new entity fields appear
spring.opencrawling.output.iceberg.partition-by YEAR_MONTH Partition transform: YEAR_MONTH, SOURCE_AND_DATE, or NONE
spring.opencrawling.output.iceberg.compression-codec zstd Parquet compression codec: zstd, snappy, gzip
spring.opencrawling.output.iceberg.s3.endpoint (empty) S3-compatible endpoint (e.g. http://minio:9000)
spring.opencrawling.output.iceberg.s3.path-style-access true Enables path-style bucket access for MinIO and custom S3 stores

8. Testing, Decoupled Stack & DuckDB OLAP Queries

The Apache Iceberg Output Connector includes complete turnkey automated test suites:

# 1. Test standalone REST catalog interaction & dynamic schema evolution
./scripts/test-iceberg-output-connector.sh

# 2. Test full multi-service decoupled ingestion pipeline (Kafka, MinIO, REST Catalog)
./scripts/test-iceberg-output-decoupled.sh

Once ingested, you can query your lakehouse tables immediately with in-process DuckDB without running heavy analytical clusters:

-- Querying ingested enterprise documents in DuckDB
INSTALL iceberg;
LOAD iceberg;

SELECT document_id, title, action, security_owner, allowed_principals, entity_vendor
FROM iceberg_scan('s3://warehouse/enterprise_data/documents',
                  endpoint='http://localhost:9000',
                  access_key_id='admin',
                  secret_access_key='password')
WHERE 'role:lakehouse-analytics' = ANY(allowed_principals);

Stream Enterprise Knowledge into Apache Iceberg

Try the new Apache Iceberg Output Connector in our interactive browser simulator, or explore the source code on GitHub.