This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Connectors

Catalogue of built-in connectors and how to configure them.

For conceptual documentation — what a Connector is, the lifecycle design, and how Connectors are decoupled from downstream components — see Architecture: Connector.

Configuring a Connector

To configure a Connector, provide its class and name in the config. Optionally specify the pipeline it feeds, a docIdPrefix for ID namespacing, and whether it requires document collapsing:

{
  name: "my-connector"
  class: "com.kmwllc.lucille.connector.FileConnector"
  pipeline: "my-pipeline"
  docIdPrefix: "files-"
  paths: ["/data/files"]
}

Common Parameters

These parameters are available on all Connectors via AbstractConnector:

ParameterRequiredDescription
classYesFully qualified class name of the Connector implementation.
nameYesConnector name for logging and run summaries.
pipelineNoName of the pipeline to process this connector’s documents. If omitted, no Workers or Indexer are started for this connector.
docIdPrefixNoString prefix prepended to every Document ID to prevent collisions across connectors.
collapseNoWhether the Publisher should collapse consecutive documents with the same ID (for CDC scenarios). Default: false.

Sequencing Multiple Connectors

A single Lucille run can chain multiple Connectors in sequence. Each Connector runs to completion (all its documents processed and indexed) before the next begins:

connectors: [
  { name: "parent-docs",  class: "...", pipeline: "pipeline1" },
  { name: "child-docs",   class: "...", pipeline: "pipeline1" }
]

Lucille Connectors (Core)

ConnectorDescription
File ConnectorTraverses local, S3, Azure, or GCS file systems and publishes documents. Supports CSV, JSON, XML file handlers, incremental mode, and tombstone deletions.
Database ConnectorReads rows from any JDBC-compatible database.
Kafka ConnectorReads documents from a Kafka topic as a data source.
RSS ConnectorPublishes documents from an RSS feed, with optional incremental refresh.
Sequence Connector (source only)Generates a configurable number of empty Documents. Useful for testing. Requires numDocs; accepts optional startWith.
Solr ConnectorReads documents from a Solr collection using cursor-based pagination. Supports pre/post update actions.

The following connectors are deprecated. Use FileConnector with a corresponding FileHandler instead.

ConnectorReplacement
CSV Connector (Deprecated)FileConnector with csv FileHandler
JSON Connector (Deprecated)FileConnector with json FileHandler
XML Connector (Deprecated)FileConnector with xml FileHandler

Lucille Connectors (Plugins)

ConnectorDescription
Parquet ConnectorReads Apache Parquet files and publishes each row as a Document. Requires lucille-parquet dependency.

File Handler configuration (CSV, JSON, XML, custom) is documented on the File Connector page.

1 - File Connector

A Connector that traverses local filesystems and cloud storage (S3, Azure, GCS), applies pluggable file handlers, and publishes Lucille documents. Supports incremental mode, tombstone deletions, and archive unpacking.

Source Code

The FileConnector traverses a file system and publishes a Lucille Document for each file it encounters. It supports local filesystems, Amazon S3, Azure Blob Storage, and Google Cloud Storage through a unified interface — a single connector config can traverse paths across multiple providers simultaneously. Optional File Handlers extract structured Documents from files that themselves contain data (CSV rows, JSON objects, XML elements).


Cloud Storage Configuration

When traversing cloud storage, provide authentication under the appropriate top-level config block alongside your connector config. Each provider also accepts an optional maxNumOfPages to limit how many file listings are loaded into memory per request.

Azure

azure {
  connectionString: "DefaultEndpointsProtocol=https;AccountName=..."
  # or:
  accountName: "myaccount"
  accountKey: "mykey"
  maxNumOfPages: 100
}

You must provide either connectionString, or both accountName and accountKey.

Google Cloud Storage

gcp {
  pathToServiceKey: "/path/to/service-account.json"
  maxNumOfPages: 100
}

Amazon S3

s3 {
  accessKeyId: "AKIA..."
  secretAccessKey: "..."
  region: "us-east-1"
  maxNumOfPages: 100
}

For S3 paths, percent-encode special characters in paths (e.g., s3://bucket/folder%20with%20spaces).

A single connector can traverse multiple paths across providers:

paths: ["file:///local/data", "s3://my-bucket/prefix", "az://container/path"]

The URI scheme selects the appropriate storage backend automatically. A local filesystem client is always available without additional configuration.


File Handlers

File Handlers process individual files and extract one or more Lucille Documents from their contents. Without a handler, the FileConnector publishes one document per file containing only the file metadata fields. With a handler, a CSV file becomes one document per row, a JSON file becomes one document per object, and so on.

File Handlers are configured under the fileHandlers block. The key (csv, json, xml) determines which handler applies to files with that extension. fileHandlers and fileOptions are two separate top-level keys in the connector config block — they are not nested inside each other.

fileHandlers: {
  csv {
    separatorChar: "|"
    docIdPrefix: "csv-"
  }
  json {}
  xml {
    chunkPath: "//record"
  }
}

All File Handlers support docIdPrefix (prepended to every generated Document ID).

CSV File Handler

com.kmwllc.lucille.core.fileHandler.CSVFileHandler

Extracts one Document per row from a CSV file.

ParameterTypeDefaultDescription
idFieldStringSingle column name whose value becomes the Document ID.
idFieldsList<String>Multiple column names combined to form the Document ID.
docIdFormatStringJava String.format pattern for constructing the Document ID from column values.
lineNumberFieldStringcsvLineNumberField name for storing the row’s line number.
filenameFieldStringfilenameField name for storing the source filename.
filePathFieldStringsourceField name for storing the full file path.
separatorCharString,Column delimiter character.
useTabsBooleanfalseUse tab as delimiter (overrides separatorChar).
interpretQuotesBooleantrueTreat " as a quoting character.
ignoreEscapeCharBooleanfalseDisable backslash escape handling.
lowercaseFieldsBooleanfalseConvert column header names to lowercase field names.
ignoredTermsList<String>Column values matching these strings are excluded from the document.
docIdPrefixStringPrefix prepended to every Document ID.
fileHandlers: {
  csv {
    idField: "article_id"
    separatorChar: "|"
    filenameField: "source_file"
    lowercaseFields: true
    docIdPrefix: "article-"
  }
}

JSON File Handler

com.kmwllc.lucille.core.fileHandler.JSONFileHandler

Extracts one Document per JSON object. Supports both standard JSON files (a single object or array) and JSON Lines (.jsonl) format where each line is a separate JSON object.

ParameterTypeDefaultDescription
idFieldStringJSON field whose value becomes the Document ID.
idFieldsList<String>Multiple JSON fields combined to form the Document ID.
docIdFormatStringJava String.format pattern for constructing the Document ID.
blacklistList<String>JSON fields to exclude from the Document.
whitelistList<String>Only include these JSON fields on the Document.
docIdPrefixStringPrefix prepended to every Document ID.
fileHandlers: {
  json {
    idField: "doc_id"
    blacklist: ["internal_metadata", "_rev"]
    docIdPrefix: "doc-"
  }
}

XML File Handler

com.kmwllc.lucille.core.fileHandler.XMLFileHandler

Extracts Documents from an XML file by selecting elements matching an XPath expression. Each matched element becomes a Document; element child text content and attributes become Document fields.

ParameterTypeRequiredDescription
chunkPathStringYesXPath expression selecting the XML elements to convert into Documents (e.g., "//record", "/root/items/item").
docIdPrefixStringNoPrefix prepended to every Document ID.
fileHandlers: {
  xml {
    chunkPath: "//product"
  }
}

Video File Handler

Available via the lucille-video plugin (com.kmwllc:lucille-video). Requires FFmpeg to be installed on the system.

Extracts frames from video files as individual Lucille Documents. See the plugin documentation for configuration parameters and supported formats.

Custom File Handlers

To process a file format not covered by the built-in handlers:

  1. Extend BaseFileHandler.
  2. Declare public static final Spec SPEC = SpecBuilder.fileHandler()... (required).
  3. Implement Iterator<Document> processFile(InputStream stream, String pathStr).

Reference the handler by its fully-qualified class name in the fileHandlers block. The class field can also override the built-in handler for a standard extension:

fileHandlers: {
  csv {
    class: "com.example.MyCustomCSVHandler"
    myCustomParam: "value"
  }
}

File Handlers accept an InputStream and return Documents via an Iterator. The InputStream and any underlying resources are closed when the Iterator’s hasNext() returns false. When working directly with File Handlers in code, always exhaust the returned Iterator.


File Options

File options control traversal behaviour — how the connector handles the files it finds.

OptionTypeDescription
getFileContentBooleanIf true, reads the file’s raw bytes into the file_content field. Downloads the file on cloud storage. Slows traversal significantly.
handleArchivedFilesBooleanIf true, unpacks archive files (zip, tar, tar.gz) and traverses their contents. Downloads the archive on cloud storage.
handleCompressedFilesBooleanIf true, decompresses compressed files (gz) before processing.
moveToAfterProcessingStringPath to move each file to after successful processing. Single-path configurations only — cannot be combined with multiple paths.
moveToErrorFolderStringPath to move a file to if an error occurs during processing. Same single-path constraint applies.

When archive or compressed file handling is enabled, entries inside archives get composite paths using the ! separator:

s3://bucket/archive.zip!path/inside/archive/file.csv

Modification and publish cutoffs apply to both the container archive and its entries.


Filter Options

Filter options control which files are processed and published. All filter options are optional. When multiple options are specified, a file must satisfy all of them to be processed. Evaluation order:

  1. File name must match at least one includes pattern (if any are specified).
  2. File name must not match any excludes pattern.
  3. File’s modification time must fall within lastModifiedCutoff (if specified).
  4. File must not have been published within lastPublishedCutoff (if specified — requires state configuration).
  5. In incremental publish mode, only new or modified files since the last run are published (requires state configuration).
OptionTypeDescription
includesList<String>Regex patterns — only file names matching at least one pattern are processed.
excludesList<String>Regex patterns — file names matching any pattern are skipped.
lastModifiedCutoffStringDuration string (e.g., "24h", "7d") — only files modified within this window are processed.
lastPublishedCutoffStringDuration string — files published by Lucille within this window are skipped. Requires state.
publishModeStringFULL (default) or INCREMENTAL. In incremental mode, only new or modified files are published. Requires state.
sendTombstonesBooleanIf true, publishes tombstone documents for files that have been deleted since the last run. Requires incremental mode.

Example using regex patterns and time-based filtering:

filterOptions: {
  includes: [".*\\.pdf$", ".*\\.docx$"]
  excludes: [".*\\.tmp$", ".*~$"]
  lastModifiedCutoff: "24h"
  lastPublishedCutoff: "7d"
  publishMode: "incremental"
}

Incremental Mode and State

The FileConnector can persist state to a JDBC-compatible database to track which files have been published and when. State enables lastPublishedCutoff, incremental publish mode, and tombstone detection.

Configure a state database alongside the connector:

state {
  enabled: true                    # Set to false to disable state without removing the block
  driver: "org.h2.Driver"          # Default: embedded H2
  connectionString: "jdbc:h2:./lucille-state"
  jdbcUser: ""
  jdbcPassword: ""
  tableName: "file_state"          # Defaults to the connector name
  performDeletions: true
  pathLength: 200                  # Max length of the file path column
}

If connectionString is omitted, an embedded H2 database is created at ./state/{CONNECTOR_NAME}.

A few constraints to be aware of when using state:

  • Files that are moved or renamed will not have lastPublishedCutoff applied — their new path is not recognised as previously published.
  • Capitalise directory names in paths consistently across runs. State lookups are case-sensitive.
  • Each database table should be used by only one connector configuration. Sharing a table across connectors will corrupt state. This can happen in several ways: reusing the same explicit tableName across multiple connectors in the same config file; running two separate configs concurrently where both reference the same tableName; or omitting tableName in two different configs that happen to use the same connector name (since tableName defaults to the connector name). In all cases, two connectors writing to the same state table will overwrite each other’s records, leading to incorrect incremental behavior — files may be skipped or reprocessed unexpectedly.

Tombstone Generation

When filterOptions.sendTombstones: true is set (requires incremental mode and state), the connector detects files that existed in the previous run but are no longer present. For each deleted file it publishes a tombstone document — a document with file_expired: true and ___skipped: true. The skipped flag causes the tombstone to bypass all pipeline stages. Downstream, configure the Indexer to issue a delete when it encounters the marker field:

indexer {
  type: "solr"
  deletionMarkerField: "file_expired"
  deletionMarkerFieldValue: "true"
}

This parameter only applies in incremental mode.


Document Fields

Every document published by the FileConnector carries these fields:

FieldTypeDescription
file_pathStringFull path or URI to the file.
file_modification_dateInstantLast-modified timestamp of the file.
file_creation_dateInstantCreation timestamp of the file (where available).
file_size_bytesLongFile size in bytes.
file_contentbyte[]Raw file bytes. Only populated when getFileContent: true.
file_expiredBooleanSet to true on tombstone documents for deleted files.

When a File Handler (CSV, JSON, XML) processes a file, it produces child documents with their own fields rather than a single file-level document.


Implementation Notes

This section is for Component Developers implementing new connectors. Pipeline Authors can stop here.

StorageClient Pattern

The connector handles four storage backends through a StorageClient abstraction. During traversal, the appropriate client is selected by URI scheme:

private void traverseStoragePath(Publisher publisher, URI pathToTraverse) throws ConnectorException {
    String clientKey = pathToTraverse.getScheme() != null ? pathToTraverse.getScheme() : "file";
    StorageClient storageClient = storageClientMap.get(clientKey);
    // ...
    storageClient.traverse(publisher, params, stateManager);
}

StorageClient is an interface with implementations for each backend. Each implementation handles listing/paginating files, applying filter criteria, reading file content, publishing documents, and updating state. The createClients(config) factory method inspects the config for cloud provider blocks and instantiates the appropriate clients.

Lifecycle: execute → close

Initialisation of storage clients and the state manager is deferred to inside execute(), not the constructor. This avoids opening network connections before the connector actually runs:

@Override
public void execute(Publisher publisher) throws ConnectorException {
    initialize();  // Init storage clients + state manager

    for (URI resource : storageURIs) {
        traverseStoragePath(publisher, resource);
    }

    if (sendTombstones) {
        sendExpiredFileTombstones(publisher);
    }
}

@Override
public void close() {
    if (stateManager != null) stateManager.shutdown();
    for (StorageClient client : storageClientMap.values()) {
        client.shutdown();
    }
}

SPEC Declaration

The FileConnector SPEC demonstrates how to declare a complex, nested configuration:

public static final Spec SPEC = SpecBuilder.connector()
    .requiredList("paths", new TypeReference<List<String>>(){})
    .optionalParent(
        SpecBuilder.parent("filterOptions")
            .optionalList("includes", new TypeReference<List<String>>(){})
            .optionalList("excludes", new TypeReference<List<String>>(){})
            .optionalString("lastModifiedCutoff", "lastPublishedCutoff", "publishMode", "sendTombstones").build(),
        SpecBuilder.parent("fileOptions")
            .optionalBoolean("getFileContent", "handleArchivedFiles", "handleCompressedFiles")
            .optionalString("moveToAfterProcessing", "moveToErrorFolder").build(),
        SpecBuilder.parent("state")
            .optionalString("driver", "connectionString", "jdbcUser", "jdbcPassword", "tableName")
            .optionalBoolean("performDeletions")
            .optionalNumber("pathLength").build(),
        GCP_PARENT_SPEC,
        AZURE_PARENT_SPEC,
        S3_PARENT_SPEC)
    .optionalParent("fileHandlers", new TypeReference<Map<String, Map<String, Object>>>(){})
    .build();

Key patterns: SpecBuilder.connector() provides base properties; nested SpecBuilder.parent(...) blocks define each optional config section; cloud provider specs are defined as reusable constants; fileHandlers uses a TypeReference because its structure is dynamic (keys are file extensions).

Reusable Patterns for Other Complex Connectors

  1. StorageClient pattern — abstract the data source behind an interface; select implementation by URI scheme or config key.
  2. Deferred initialisation — don’t open connections in the constructor; do it in execute().
  3. Constraint validation early — check for incompatible config combinations (e.g., multiple paths + moveToAfterProcessing) in the constructor so failures surface before any traversal begins.
  4. Nested SPEC declarations — group related config into parent blocks; define cloud provider specs as reusable constants shared across connectors.
  5. JDBC-backed state — make state optional; check config.hasPath("state") before constructing the state manager.
  6. Filter pipeline — layer multiple filter criteria (regex, time-based, state-based) with a clear evaluation order.
  7. Tombstone generation — detect deletions by comparing the current file set to the previous state snapshot; mark documents with a field the Indexer can act on.
  8. FileHandler delegation — use pluggable handlers (with their own SPECs) for format-specific processing rather than embedding format logic in the connector.

2 - Database Connector

A Connector that reads rows from a JDBC-compatible database and publishes each row as a Lucille Document.

Source Code

The DatabaseConnector reads rows from any JDBC-compatible relational database and publishes each row as a Lucille Document. Column names become field names on the Document.

Basic Configuration

connectors: [
  {
    name: "db-connector"
    class: "com.kmwllc.lucille.connector.jdbc.DatabaseConnector"
    pipeline: "my-pipeline"

    driver: "org.postgresql.Driver"
    connectionString: "jdbc:postgresql://localhost:5432/mydb"
    jdbcUser: "username"
    jdbcPassword: ${?DB_PASSWORD}
    sql: "SELECT id, title, body, published_at FROM articles WHERE active = true"
    idField: "id"
  }
]

Configuration Parameters

ParameterTypeRequiredDescription
driverStringYesJDBC driver class name. The driver JAR must be on the classpath.
connectionStringStringYesJDBC connection URL.
jdbcUserStringNoDatabase username.
jdbcPasswordStringNoDatabase password. Use ${?VAR} for environment variable substitution.
sqlStringYesSELECT statement to execute. All returned rows are published as Documents.
idFieldStringNoColumn whose value becomes the Document ID. If omitted, a UUID is generated per row.
docIdPrefixStringNoPrefix prepended to every Document ID.
fetchSizeIntegerNoJDBC fetch size hint for streaming large result sets. For MySQL, set to Integer.MIN_VALUE (i.e., -2147483648) to avoid buffering the full result set in memory.
preSQLStringNoA SQL statement (INSERT, DELETE, UPDATE, or DDL) executed once before the main query. Useful for creating temp tables, acquiring locks, or seeding data.
postSQLStringNoA SQL statement executed once after the main query completes successfully. Useful for cleanup, releasing locks, or writing completion markers.
otherSQLsList<String>NoAdditional SELECT queries to JOIN onto the primary result. Each query must return rows ordered by its join key.
otherJoinFieldsList<String>NoJoin fields parallel to otherSQLs. Required when otherSQLs is specified. Must be integer-valued columns.
ignoreColumnsList<String>NoColumn names to skip when populating Documents.
connectionRetriesInteger1Number of connection retry attempts on failure.
connectionRetryPauseInteger10000Milliseconds to wait between connection retries.

Pre and Post SQL

Use preSQL and postSQL to run setup and teardown logic that must happen before and after the main query:

{
  name: "db-connector"
  class: "com.kmwllc.lucille.connector.jdbc.DatabaseConnector"
  pipeline: "my-pipeline"

  driver: "org.postgresql.Driver"
  connectionString: "jdbc:postgresql://localhost:5432/mydb"
  jdbcUser: ${?DB_USER}
  jdbcPassword: ${?DB_PASSWORD}

  preSQL: "CREATE TEMP TABLE export_snapshot AS SELECT * FROM articles WHERE active = true"
  sql: "SELECT id, title, body FROM export_snapshot ORDER BY id"
  postSQL: "DROP TABLE IF EXISTS export_snapshot"
  idField: "id"
}

postSQL runs only if preSQL and the main query both succeed. If the connector throws, postSQL is skipped but close() is always called.

Multi-Query Joins

otherSQLs allows you to enrich primary rows with data from additional queries at read time, without a SQL JOIN. Each secondary query must be ordered by its join key (which must be an integer column matching a column in the primary query).

{
  sql: "SELECT id, title FROM articles ORDER BY id"
  idField: "id"
  otherSQLs: ["SELECT article_id, tag_name FROM article_tags ORDER BY article_id"]
  otherJoinFields: ["article_id"]
}

For each primary row, the connector merges matching rows from otherSQLs as multi-valued fields onto the Document.

Incremental Ingest

The DatabaseConnector does not maintain internal state. For incremental ingest (only rows modified since the last run), filter at the SQL level:

SELECT id, title, updated_at FROM articles
WHERE updated_at > TIMESTAMP '2025-01-01 00:00:00'
ORDER BY updated_at ASC

Store the high-water mark externally (e.g., in a config file, environment variable, or the database itself) and substitute it via HOCON variable substitution.

MySQL Streaming

For large MySQL tables, set fetchSize to avoid loading the entire result set into memory:

{
  driver: "com.mysql.cj.jdbc.Driver"
  connectionString: "jdbc:mysql://localhost:3306/mydb?useCursorFetch=true"
  fetchSize: -2147483648
  sql: "SELECT id, title FROM large_table ORDER BY id"
  idField: "id"
}

Common JDBC Drivers

DatabaseDriver ClassMaven Artifact
PostgreSQLorg.postgresql.Driverorg.postgresql:postgresql
MySQLcom.mysql.cj.jdbc.Drivercom.mysql:mysql-connector-j
SQL Servercom.microsoft.sqlserver.jdbc.SQLServerDrivercom.microsoft.sqlserver:mssql-jdbc
SQLiteorg.sqlite.JDBCorg.xerial:sqlite-jdbc
Apache Derbyorg.apache.derby.iapi.jdbc.AutoloadedDriverorg.apache.derby:derby
H2org.h2.Drivercom.h2database:h2

Integration with QueryDatabase Stage

For per-document database enrichment (joining a lookup table for each document mid-pipeline, rather than reading a source table), use the QueryDatabase Stage.

3 - Solr Connector

A Connector that queries Solr and publishes each result document into a Lucille pipeline. Supports pre/post actions for setup and cleanup.

Source Code

The SolrConnector issues a query against a Solr collection and publishes each result as a Lucille Document. It is useful for cross-index enrichment pipelines — for example, reading documents from one Solr collection, enriching them, and indexing them into a different collection or backend.

Internally, the connector uses cursor-based pagination (cursorMark) to iterate through large result sets without loading the full result into memory. Results are sorted by idField ascending (required for cursor pagination).

Basic Configuration

connectors: [
  {
    name: "solr-source"
    class: "com.kmwllc.lucille.connector.SolrConnector"
    pipeline: "my-pipeline"

    solr {
      url: ["http://localhost:8983/solr"]
      useCloudClient: true
      defaultCollection: "source-collection"
    }

    solrParams {
      q: "*:*"
      fl: "id,title,body,author"
      fq: "status:active"
      rows: 100
    }

    idField: "id"
  }
]

Configuration Parameters

ParameterTypeRequiredDescription
solrObjectYesSolr connection parameters (see below).
solrParamsMap<String, Object>NoSolr query parameters passed directly to the query. Used when pipeline is configured.
preActionsList<String>NoSolr update requests to send before executing the main query (see below).
postActionsList<String>NoSolr update requests to send after executing the main query (see below).
useXmlBooleanNoIf true, sends action requests as XML instead of JSON. Default: false.
idFieldStringNoSolr field to use as the Lucille Document ID. Default: id.

solr Connection Parameters

ParameterTypeRequiredDescription
urlList<String>YesSolr URL(s). For basic mode, include the collection (e.g., http://localhost:8983/solr/my-collection). For cloud mode, omit the collection.
useCloudClientBooleanNoUse CloudHttp2SolrClient (SolrCloud). Default: false.
defaultCollectionStringNoDefault collection name for cloud mode.
zkHostsList<String>NoZooKeeper hosts for SolrCloud connection (alternative to url in cloud mode).
zkChrootStringNoZooKeeper chroot path (e.g., /solr).
userNameStringNoBasic auth username.
passwordStringNoBasic auth password.
acceptInvalidCertBooleanNoAccept self-signed or invalid SSL certificates.

Query Parameters (solrParams)

The solrParams block accepts any standard Solr query parameters:

solrParams {
  q: "status:active AND type:article"
  fl: "id,title,body,published_date"
  fq: ["category:news", "language:en"]
  rows: 500
}

Common parameters:

  • q — query string (default: *:* if omitted)
  • fl — field list (fields to return; omit for all fields)
  • fq — filter query (can be a single string or a list for multiple filters)
  • rows — page size for cursor pagination (default: Solr’s default, typically 10)

The connector always adds sort: {idField} asc automatically (required for cursor pagination) and manages cursorMark internally.

Pre and Post Actions

preActions and postActions are lists of Solr update requests sent before and after the main query, respectively. They support the {runId} placeholder, which is replaced with the current run’s UUID at execution time.

The request format is controlled by useXml:

  • useXml: false (default): requests are JSON strings
  • useXml: true: requests are XML strings

Example: Mark documents as in-progress before reading, mark as done after

connectors: [
  {
    name: "solr-source"
    class: "com.kmwllc.lucille.connector.SolrConnector"
    pipeline: "process-pipeline"

    solr {
      url: ["http://localhost:8983/solr/my-collection"]
    }

    # JSON update — set run_id on all active documents before reading
    preActions: [
      "{\"add\":{\"doc\":{\"id\":\"marker\",\"run_id\":\"{runId}\"}}}"
    ]

    # JSON update — clean up after run
    postActions: [
      "{\"delete\":{\"query\":\"run_id:{runId}\"}}"
    ]

    solrParams {
      q: "status:active"
      fl: "id,title,body"
    }
  }
]

postActions run only if preActions and the main query both succeed. If the connector throws, postActions are skipped.

Cursor-Based Pagination

The connector iterates through results using Solr’s cursorMark mechanism, which avoids deep pagination performance issues. This requires:

  1. Documents to be sorted by a unique field (the idField).
  2. The idField to be indexed in Solr as a single-valued, non-analyzed field.

For large collections, tune rows in solrParams to balance memory use and round-trip count.

4 - Kafka Connector

A Connector that reads Documents from a Kafka topic and publishes them into the Lucille pipeline.

Source Code

The KafkaConnector reads Documents from a Kafka topic and publishes them into a Lucille pipeline. This is distinct from Kafka’s role as the messaging layer in distributed mode — the KafkaConnector is a data source, reading documents produced by an upstream system.

Use Cases

  • Streaming ingest from Kafka: An upstream application publishes documents (as JSON) to a Kafka topic, and Lucille reads them for enrichment and indexing.
  • Connectorless distributed mode: In this deployment pattern, a third-party publisher puts documents onto a Kafka source topic, and Lucille Workers consume them directly. In this case the KafkaConnector is not used — Workers listen to the source topic directly.

Configuration

All Kafka connection parameters are nested under the kafka key within the connector config block.

connectors: [
  {
    name: "kafka-source"
    class: "com.kmwllc.lucille.connector.KafkaConnector"
    pipeline: "my-pipeline"

    kafka.bootstrapServers: "kafka1:9092,kafka2:9092"
    kafka.topic: "my-source-topic"
    kafka.consumerGroupId: "lucille-kafka-connector"
    kafka.clientId: "lucille-consumer-1"
    kafka.maxPollIntervalSecs: 600
    idField: "article_id"
    maxMessages: 10000
  }
]

Configuration Parameters

ParameterTypeRequiredDescription
kafka.bootstrapServersStringYesComma-separated list of Kafka broker addresses.
kafka.topicStringYesKafka topic to consume from.
kafka.consumerGroupIdStringYesConsumer group ID.
kafka.clientIdStringYesKafka client identifier for logging and monitoring.
kafka.maxPollIntervalSecsIntegerYesMaximum time between Kafka polls before the consumer is evicted from the consumer group.
idFieldStringNoJSON field in the Kafka message to use as the Document ID. If omitted, a UUID is generated.
kafka.documentDeserializerStringNoFully-qualified class name of a custom Deserializer<Document>. Defaults to the built-in JSON deserializer.
maxMessagesLongNoMaximum number of messages to consume before stopping. If omitted, runs until no more messages are available.
messageTimeoutLongNoKafka poll timeout in milliseconds. Default: 100.
offsetsMap<Integer, Long>NoMap of partition numbers to starting offsets. If omitted, uses the consumer group’s committed offset.
continueOnTimeoutBooleanNoIf true, continue polling after a poll timeout instead of stopping.

Message Format

The KafkaConnector expects each Kafka message value to be a JSON object. Each JSON object becomes a Lucille Document. Field names in the JSON map directly to Document field names.

Example Kafka message:

{
  "article_id": "art-001",
  "title": "Breaking News",
  "body": "Full article text...",
  "published_at": "2025-06-01T12:00:00Z"
}

Security

For Kafka clusters with TLS or SASL authentication, use the top-level kafka {} block (separate from the connector’s inline params) to provide properties files and security settings:

kafka {
  bootstrapServers: "kafka1:9092"
  securityProtocol: "SSL"
  consumerPropertyFile: "/path/to/consumer.properties"
  producerPropertyFile: "/path/to/producer.properties"
  adminPropertyFile: "/path/to/admin.properties"
}

securityProtocol, consumerPropertyFile, producerPropertyFile, and adminPropertyFile are properties of the top-level kafka {} block and apply to all Kafka communication in the process, not just the KafkaConnector.

Kafka as the Messaging Layer vs. as a Source

These are two distinct uses of Kafka in Lucille:

RoleDescriptionConfiguration
Source (KafkaConnector)Reads application data from a Kafka topic.Use KafkaConnector in your connectors list.
Messaging layerCarries Documents between Lucille components in distributed mode.Add -usekafka flag to the Runner; configure the kafka {} block.

Both can be active simultaneously: a KafkaConnector reads data from one topic while Lucille’s distributed messaging uses separate internal topics.

5 - Parquet Connector

A Connector that reads Apache Parquet files and publishes each row as a Lucille Document.

Source Code

The ParquetConnector reads Apache Parquet files — locally or from Amazon S3 — and publishes each row as a Lucille Document. Parquet is a columnar format commonly used to store pre-computed embeddings, feature vectors, and large datasets.

Maven dependency:

<dependency>
  <groupId>com.kmwllc</groupId>
  <artifactId>lucille-parquet</artifactId>
  <version>${lucille.version}</version>
</dependency>

Configuration

connectors: [
  {
    name: "parquet-source"
    class: "com.kmwllc.lucille.parquet.connector.ParquetConnector"
    pipeline: "my-pipeline"
    pathToStorage: "/data/embeddings.parquet"
    idField: "doc_id"
    fsUri: "file:///"
  }
]

Configuration Parameters

ParameterTypeRequiredDescription
pathToStorageStringYesPath to a Parquet file or directory to traverse for .parquet files.
idFieldStringYesField name in the Parquet schema to use as the Document ID. Must exist in the file’s schema.
fsUriStringYesURI for the filesystem to use (e.g., "file:///" for local, "s3a://my-bucket" for S3).
s3KeyStringNoAWS S3 access key. Required when using S3.
s3SecretStringNoAWS S3 secret key. Required when using S3.
limitLongNoMaximum number of Documents to publish. Default: no limit.
startLongNoNumber of rows to skip from the beginning of each file. Default: 0.

S3 Configuration

For S3, provide the filesystem URI and credentials:

connectors: [
  {
    name: "parquet-s3"
    class: "com.kmwllc.lucille.parquet.connector.ParquetConnector"
    pipeline: "my-pipeline"
    pathToStorage: "/prefix/embeddings"
    idField: "doc_id"
    fsUri: "s3a://my-bucket"
    s3Key: ${AWS_ACCESS_KEY_ID}
    s3Secret: ${AWS_SECRET_ACCESS_KEY}
  }
]

Notes

  • The connector uses Hadoop’s filesystem abstraction (FileSystem) for path traversal. Unlike FileConnector, it does not use Lucille’s StorageClient infrastructure.
  • Parquet files must have the .parquet extension to be processed.
  • When paginating with start/limit, it is recommended to use individual Connectors for each Parquet file rather than a directory path.
  • The Parquet format requires random-access reads (not sequential streaming), which is why it is implemented as a standalone Connector rather than a FileConnector FileHandler.

6 - RSS Connector

A Connector that publishes Documents representing items found in an RSS feed.

The RSSConnector

The RSSConnector allows you to publish Documents representing the items found in an RSS feed of your choice. Each Document will (optionally) contain fields from the RSS items, like the author, description, title, etc. By default, the Document IDs will be the item’s guid, which should be a unique identifier for the RSS item.

You can configure the RSSConnector to only publish recent RSS items, based on the pubDate found on the items. Also, it can run incrementally, refreshing the RSS feed after a certain amount of time until you manually stop it. The RSSConnector will avoid publishing Documents for the same RSS item more than once.

The Documents published may have any of the following fields, depending on how the RSS feed is structured:

  • author (String)
  • categories (List<String>)
  • comments (List<String>)
  • content (String)
  • description (String)
  • enclosures (List<JsonNode>). Each JsonNode contains:
    • type (String)
    • url (String)
    • May contain length (Long)
  • guid (String)
  • isPermaLink (Boolean)
  • link (String)
  • title (String)
  • pubDate (Instant)