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.
| Parameter | Type | Default | Description |
|---|
idField | String | — | Single column name whose value becomes the Document ID. |
idFields | List<String> | — | Multiple column names combined to form the Document ID. |
docIdFormat | String | — | Java String.format pattern for constructing the Document ID from column values. |
lineNumberField | String | csvLineNumber | Field name for storing the row’s line number. |
filenameField | String | filename | Field name for storing the source filename. |
filePathField | String | source | Field name for storing the full file path. |
separatorChar | String | , | Column delimiter character. |
useTabs | Boolean | false | Use tab as delimiter (overrides separatorChar). |
interpretQuotes | Boolean | true | Treat " as a quoting character. |
ignoreEscapeChar | Boolean | false | Disable backslash escape handling. |
lowercaseFields | Boolean | false | Convert column header names to lowercase field names. |
ignoredTerms | List<String> | — | Column values matching these strings are excluded from the document. |
docIdPrefix | String | — | Prefix 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.
| Parameter | Type | Default | Description |
|---|
idField | String | — | JSON field whose value becomes the Document ID. |
idFields | List<String> | — | Multiple JSON fields combined to form the Document ID. |
docIdFormat | String | — | Java String.format pattern for constructing the Document ID. |
blacklist | List<String> | — | JSON fields to exclude from the Document. |
whitelist | List<String> | — | Only include these JSON fields on the Document. |
docIdPrefix | String | — | Prefix 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.
| Parameter | Type | Required | Description |
|---|
chunkPath | String | Yes | XPath expression selecting the XML elements to convert into Documents (e.g., "//record", "/root/items/item"). |
docIdPrefix | String | No | Prefix 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:
- Extend
BaseFileHandler. - Declare
public static final Spec SPEC = SpecBuilder.fileHandler()... (required). - 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.
| Option | Type | Description |
|---|
getFileContent | Boolean | If true, reads the file’s raw bytes into the file_content field. Downloads the file on cloud storage. Slows traversal significantly. |
handleArchivedFiles | Boolean | If true, unpacks archive files (zip, tar, tar.gz) and traverses their contents. Downloads the archive on cloud storage. |
handleCompressedFiles | Boolean | If true, decompresses compressed files (gz) before processing. |
moveToAfterProcessing | String | Path to move each file to after successful processing. Single-path configurations only — cannot be combined with multiple paths. |
moveToErrorFolder | String | Path 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:
- File name must match at least one
includes pattern (if any are specified). - File name must not match any
excludes pattern. - File’s modification time must fall within
lastModifiedCutoff (if specified). - File must not have been published within
lastPublishedCutoff (if specified — requires state configuration). - In
incremental publish mode, only new or modified files since the last run are published (requires state configuration).
| Option | Type | Description |
|---|
includes | List<String> | Regex patterns — only file names matching at least one pattern are processed. |
excludes | List<String> | Regex patterns — file names matching any pattern are skipped. |
pathsToSkip | List<String> | Paths of directories to skip. While traversing, this directory and its contents will not be read, processed, or published. See note below for more. |
lastModifiedCutoff | String | Duration string (e.g., "24h", "7d") — only files modified within this window are processed. |
lastPublishedCutoff | String | Duration string — files published by Lucille within this window are skipped. Requires state. |
publishMode | String | FULL (default) or INCREMENTAL. In incremental mode, only new or modified files are published. Requires state. |
sendTombstones | Boolean | If 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"
}
Note on pathsToSkip: while you could create an excludes regex detailing directories you do not want to publish files for, there
are performance benefits to using pathsToSkip instead. With this parameter, the FileConnector will not actually traverse the path and its contents.
If you use excludes, the path will be traversed, but the individual documents are not processed & published.
Each entry may be a URI with a scheme (for example s3://bucket/path, gs://bucket/path, or file:///path/to/dir). As a convenience,
local file system paths may also be provided without a scheme - either absolute (e.g. /path/to/dir) or relative to the working directory
(e.g. path/to/dir) - and will be resolved to an absolute file:// path.
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
runsBeforeExpiration: 1 # Consecutive absent runs before tombstone (default: 1)
}
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. - Similarly, remember that the files in a directory that is skipped (by
pathsToSkip), since they are not traversed, are also not tracked at all during a stateful run. - 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"
}
By default, a file is considered expired after being absent from a single run. You can increase this threshold with state.runsBeforeExpiration to provide a safety margin against transient storage issues (e.g., a temporary listing failure):
state {
runsBeforeExpiration: 3 # File must be absent for 3 consecutive runs before tombstone
}
The value must be at least 1. When set to 1 (default), a file missing from one run immediately triggers a tombstone — the original behavior.
This parameter only applies in incremental mode.
Document Fields
Every document published by the FileConnector carries these fields:
| Field | Type | Description |
|---|
file_path | String | Full path or URI to the file. |
file_modification_date | Instant | Last-modified timestamp of the file. |
file_creation_date | Instant | Creation timestamp of the file (where available). |
file_size_bytes | Long | File size in bytes. |
file_content | byte[] | Raw file bytes. Only populated when getFileContent: true. |
file_expired | Boolean | Set 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
- StorageClient pattern — abstract the data source behind an interface; select implementation by URI scheme or config key.
- Deferred initialisation — don’t open connections in the constructor; do it in
execute(). - Constraint validation early — check for incompatible config combinations (e.g., multiple paths +
moveToAfterProcessing) in the constructor so failures surface before any traversal begins. - Nested SPEC declarations — group related config into
parent blocks; define cloud provider specs as reusable constants shared across connectors. - JDBC-backed state — make state optional; check
config.hasPath("state") before constructing the state manager. - Filter pipeline — layer multiple filter criteria (regex, time-based, state-based) with a clear evaluation order.
- 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.
- 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
| Parameter | Type | Required | Description |
|---|
driver | String | Yes | JDBC driver class name. The driver JAR must be on the classpath. |
connectionString | String | Yes | JDBC connection URL. |
jdbcUser | String | No | Database username. |
jdbcPassword | String | No | Database password. Use ${?VAR} for environment variable substitution. |
sql | String | Yes | SELECT statement to execute. All returned rows are published as Documents. |
idField | String | No | Column whose value becomes the Document ID. If omitted, a UUID is generated per row. |
docIdPrefix | String | No | Prefix prepended to every Document ID. |
fetchSize | Integer | No | JDBC 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. |
preSQL | String | No | A SQL statement (INSERT, DELETE, UPDATE, or DDL) executed once before the main query. Useful for creating temp tables, acquiring locks, or seeding data. |
postSQL | String | No | A SQL statement executed once after the main query completes successfully. Useful for cleanup, releasing locks, or writing completion markers. |
otherSQLs | List<String> | No | Additional SELECT queries to JOIN onto the primary result. Each query must return rows ordered by its join key. |
otherJoinFields | List<String> | No | Join fields parallel to otherSQLs. Required when otherSQLs is specified. Must be integer-valued columns. |
ignoreColumns | List<String> | No | Column names to skip when populating Documents. |
connectionRetries | Integer | 1 | Number of connection retry attempts on failure. |
connectionRetryPause | Integer | 10000 | Milliseconds 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
| Database | Driver Class | Maven Artifact |
|---|
| PostgreSQL | org.postgresql.Driver | org.postgresql:postgresql |
| MySQL | com.mysql.cj.jdbc.Driver | com.mysql:mysql-connector-j |
| SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | com.microsoft.sqlserver:mssql-jdbc |
| SQLite | org.sqlite.JDBC | org.xerial:sqlite-jdbc |
| Apache Derby | org.apache.derby.iapi.jdbc.AutoloadedDriver | org.apache.derby:derby |
| H2 | org.h2.Driver | com.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
| Parameter | Type | Required | Description |
|---|
solr | Object | Yes | Solr connection parameters (see below). |
solrParams | Map<String, Object> | No | Solr query parameters passed directly to the query. Used when pipeline is configured. |
preActions | List<String> | No | Solr update requests to send before executing the main query (see below). |
postActions | List<String> | No | Solr update requests to send after executing the main query (see below). |
useXml | Boolean | No | If true, sends action requests as XML instead of JSON. Default: false. |
idField | String | No | Solr field to use as the Lucille Document ID. Default: id. |
solr Connection Parameters
| Parameter | Type | Required | Description |
|---|
url | List<String> | Yes | Solr URL(s). For basic mode, include the collection (e.g., http://localhost:8983/solr/my-collection). For cloud mode, omit the collection. |
useCloudClient | Boolean | No | Use CloudHttp2SolrClient (SolrCloud). Default: false. |
defaultCollection | String | No | Default collection name for cloud mode. |
zkHosts | List<String> | No | ZooKeeper hosts for SolrCloud connection (alternative to url in cloud mode). |
zkChroot | String | No | ZooKeeper chroot path (e.g., /solr). |
userName | String | No | Basic auth username. |
password | String | No | Basic auth password. |
acceptInvalidCert | Boolean | No | Accept 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 stringsuseXml: 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.
The connector iterates through results using Solr’s cursorMark mechanism, which avoids deep pagination performance issues. This requires:
- Documents to be sorted by a unique field (the
idField). - 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
| Parameter | Type | Required | Description |
|---|
kafka.bootstrapServers | String | Yes | Comma-separated list of Kafka broker addresses. |
kafka.topic | String | Yes | Kafka topic to consume from. |
kafka.consumerGroupId | String | Yes | Consumer group ID. |
kafka.clientId | String | Yes | Kafka client identifier for logging and monitoring. |
kafka.maxPollIntervalSecs | Integer | Yes | Maximum time between Kafka polls before the consumer is evicted from the consumer group. |
idField | String | No | JSON field in the Kafka message to use as the Document ID. If omitted, a UUID is generated. |
kafka.documentDeserializer | String | No | Fully-qualified class name of a custom Deserializer<Document>. Defaults to the built-in JSON deserializer. |
maxMessages | Long | No | Maximum number of messages to consume before stopping. If omitted, runs until no more messages are available. |
messageTimeout | Long | No | Kafka poll timeout in milliseconds. Default: 100. |
offsets | Map<Integer, Long> | No | Map of partition numbers to starting offsets. If omitted, uses the consumer group’s committed offset. |
continueOnTimeout | Boolean | No | If true, continue polling after a poll timeout instead of stopping. |
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:
| Role | Description | Configuration |
|---|
| Source (KafkaConnector) | Reads application data from a Kafka topic. | Use KafkaConnector in your connectors list. |
| Messaging layer | Carries 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.