LangChain4j Document Loaders
Load documents into LangChain4j from files, URLs, S3, GitHub and more, and parse PDF, DOCX and HTML with Apache Tika — the ingestion front-end for any RAG pipeline in Java.
On this page
Ingestion begins with getting documents into Document objects. LangChain4j separates loading
(fetching bytes from a source) from parsing (turning bytes into text), which lets you combine any
source with any format. This tutorial covers the common loaders and the metadata discipline that
makes retrieval work later.
Key Takeaways
- A loader fetches bytes; a parser extracts text. Combine them freely.
- Apache Tika parses PDF, DOCX, HTML and dozens of formats through one parser.
- Load from filesystem, URL, S3, GitHub and more, each a separate module.
- Attach metadata at load time — it is the only thing you can filter or cite on later.
Loading from the filesystem
// Apache Tika handles almost every format, so it is the sensible default parser.
DocumentParser parser = new ApacheTikaDocumentParser();
// A single file:
Document doc = FileSystemDocumentLoader.loadDocument(
Path.of("handbook.pdf"), parser);
// A whole directory (recursively), matching a glob:
List<Document> docs = FileSystemDocumentLoader.loadDocumentsRecursively(
Path.of("./knowledge-base"), parser);Parsers
The parser turns bytes into text. Tika is the generalist; specialised parsers exist for control.
// Apache Tika: PDF, DOCX, PPTX, XLSX, HTML, ODT, RTF and many more.
DocumentParser tika = new ApacheTikaDocumentParser();
// Plain text, when you know the format and want no dependency overhead.
DocumentParser text = new TextDocumentParser();Loading from remote sources
Each source is a separate module you add as needed.
DocumentSource source = AmazonS3DocumentLoader.builder()
.region("us-east-1")
.build()
.loadDocument("my-bucket", "docs/handbook.pdf", parser);Document readme = GitHubDocumentLoader.builder()
.gitHubToken(System.getenv("GITHUB_TOKEN"))
.build()
.loadDocument("owner", "repo", "main", "README.md", parser);Document page = UrlDocumentLoader.load(
"https://example.com/article", new TextDocumentParser());Attaching metadata — do not skip this
Metadata is what makes filtered retrieval and citations possible later. It must be added at load time, because it cannot be reconstructed afterwards.
public Document loadWithMetadata(Path path, String tenantId) {
Document doc = FileSystemDocumentLoader.loadDocument(path, new ApacheTikaDocumentParser());
// Everything you might later filter on or cite must be recorded now.
doc.metadata()
.put("sourceId", path.getFileName().toString())
.put("tenantId", tenantId)
.put("loadedAt", Instant.now().toString())
.put("path", path.toString());
return doc;
}From loading to the full pipeline
Loading is the first stage. The EmbeddingStoreIngestor chains loading, splitting, embedding and
storing:
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively(
Path.of("./knowledge-base"), new ApacheTikaDocumentParser());
EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(500, 100))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build()
.ingest(documents);Splitting is covered in LangChain4j text splitters, embedding in embedding models, and storage in embedding stores.
Handling large ingestions
For big corpora, load and ingest in batches rather than holding everything in memory:
try (Stream<Path> paths = Files.walk(Path.of("./knowledge-base"))) {
paths.filter(Files::isRegularFile)
.collect(batchesOf(50)) // your batching helper
.forEach(batch -> {
List<Document> docs = batch.stream()
.map(p -> loadWithMetadata(p, tenantId))
.toList();
ingestor.ingest(docs); // embed and store this batch
});
}Making ingestion idempotent
Re-running an import must not double your chunks. Delete a source's existing content before re-adding it, keyed by the metadata you attached:
embeddingStore.removeAll(metadataKey("sourceId").isEqualTo(sourceId));
ingestor.ingest(freshDocuments);Duplicate chunks quietly wreck retrieval — the same passage occupies several of your top results and crowds out the answer.
Next
Frequently Asked Questions
How do I load a PDF into LangChain4j?
What is the difference between a loader and a parser in LangChain4j?
Can LangChain4j load documents from S3 or GitHub?
Should I attach metadata when loading documents?
Related tutorials
- LangChain4j Chains & CompositionCompose multi-step LLM workflows in LangChain4j: sequential chains, routing by classification, and building custom chains from AI Services — when to chain and when a single call suffices.
- LangChain4j Text SplittersChunk documents effectively in LangChain4j: the recursive splitter, chunk size and overlap tuning, splitting code and markdown, and why chunking is the highest-impact decision in RAG.
- LangChain4j Chat ModelsConfigure chat models in LangChain4j: OpenAI, Anthropic Claude, Google Gemini, Mistral and Ollama — with streaming, timeouts, retries and how to swap providers without touching your code.
- LangChain4j Embedding ModelsConfigure embedding models in LangChain4j: hosted models like OpenAI and Cohere, free in-process ONNX models, dimension matching, and choosing an embedding model for RAG.