Apache Lucene Tutorial
A simple guide to getting started with Apache Lucene — from basic concepts to building a working search application in Java.
Introduction
Apache Lucene is a full-text search library written in Java. It is a top-level Apache Project and was written in 1999 by Doug Cutting. Lucene is a powerful library that forms the core of many search-based technologies like Elasticsearch and Solr.
Lucene is very fast — it can find a document containing a given word among millions of documents in milliseconds, with indexing speeds as high as 700GB per hour.
This tutorial will first go over the basic concepts of Apache Lucene, then explore the Lucene API, and finally build a simple search application. We will be using Lucene 8.
History of Apache Lucene
Lucene was first published in 1999 by Doug Cutting on SourceForge. In September 2001 it joined the Jakarta family of Apache Software Foundation. In January 2005, it became its own top-level project. Initially, various other projects like Mahout, Tika, and Nutch were part of Lucene before separating into their own Apache projects.
Lucene vs Solr and Elasticsearch
While Lucene is just a Java library, Elasticsearch and Solr are full-fledged search servers. Think of Lucene as the engine, Elasticsearch as a Ferrari, and Solr as a Lamborghini.
With Lucene you get core search capabilities. Elasticsearch and Solr add HTTP interfaces, distributed computing, automatic performance management, and many other production features on top.
Adding Dependencies
We need lucene-core to start. Optionally add lucene-queryparser for query parsing and lucene-analyzers-common for additional analyzers.
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>8.10.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>8.10.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>8.10.1</version>
</dependency>
A Simple Indexer
Let's create a simple indexer that adds three movies to a Lucene index:
public class SimpleIndexer {
public void index(String indexPath) throws IOException {
Directory directory = FSDirectory.open(Paths.get(indexPath));
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
Document movie1 = new Document();
movie1.add(new TextField("title", "Harry Potter and the Prisoner of Azkaban", Field.Store.YES));
Document movie2 = new Document();
movie2.add(new TextField("title", "Lord of the Rings: The fellowship of the ring.", Field.Store.YES));
Document movie3 = new Document();
movie3.add(new TextField("title", "Toy Story 3", Field.Store.YES));
indexWriter.addDocument(movie1);
indexWriter.addDocument(movie2);
indexWriter.addDocument(movie3);
indexWriter.close();
}
}
A Simple Searcher
Now let's create a searcher that finds a movie by title:
public class SimpleSearcher {
public void search(String indexPath, String title) throws IOException, ParseException {
IndexReader indexReader = DirectoryReader.open(FSDirectory.open(Paths.get(indexPath)));
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
Analyzer analyzer = new StandardAnalyzer();
QueryParser queryParser = new QueryParser("title", analyzer);
Query query = queryParser.parse(title);
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println(String.format("Found %d hits.", topDocs.totalHits.value));
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document movie = indexSearcher.doc(scoreDoc.doc);
System.out.println(String.format("Found: %s", movie.get("title")));
}
}
}
Output for query "Harry Potter":
Found 1 hits.
Found: Harry Potter and the Prisoner of Azkaban
Core Concepts
Index
All documents of similar types are stored in an index. Think of it like a database table. Internally Lucene uses an inverted index — a mapping of words to the list of documents containing them.
Documents
A document is the basic unit of search in Lucene. When you search, Lucene returns document IDs. A document is a collection of fields.
Fields
A field is a subunit of a document — think of it as an attribute or database column. For a blog index, you might have fields for title, body, date, and author. Lucene supports searching by specific fields.
Analysis
Analysis is the process of splitting text into tokens for the inverted index. The tokens generated at index time must match those generated at query time. If they don't match, you won't see correct results.
Indexing API
Directory
The Directory interface abstracts how the index is stored — file system, memory, or network. Use FSDirectory.open() for persistent indexes:
Path indexPath = Files.createTempDirectory("tempIndex");
Directory directory = FSDirectory.open(indexPath);
Field Types
Choose the right field type for your data:
- TextField — for full-text search
- StringField — for exact match
- IntPoint — for integer data
IndexWriter
The IndexWriter adds documents to the index. It needs a Directory and IndexWriterConfig:
Directory dir = FSDirectory.open(Paths.get("index"));
IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer());
IndexWriter writer = new IndexWriter(dir, config);
writer.addDocument(doc);
Analyzer
An Analyzer converts text into tokens. StandardAnalyzer splits on word boundaries, lowercases tokens, and removes stop words.
Search API
Query
The Query class is the base for all Lucene queries. Common types include TermQuery, PhraseQuery, MatchAllDocsQuery, and BooleanQuery.
QueryParser
QueryParser lets you write queries as text rather than constructing Query objects in code:
QueryParser parser = new QueryParser("body", new StandardAnalyzer());
Query query = parser.parse("body:panda");
IndexSearcher
Runs the query and returns results as a TopDocs object:
IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(directory));
TopDocs topDocs = searcher.search(query, 10);
TopDocs and ScoreDoc
TopDocs contains the total hit count and an array of ScoreDoc objects, each holding a document ID and score. Fetch the full document via:
Document doc = searcher.doc(scoreDoc.doc);
Conclusion
In this tutorial we covered what Lucene is, how it differs from Solr and Elasticsearch, built a simple Java application, and walked through the Indexing and Search APIs. You now have the foundation to build your own Lucene-powered search application.