spring-projects/spring-ai · error · RuntimeException

Failed to search index

Error message

Failed to search index

What it means

Thrown by LuceneToolIndex.doSearch() (called from search()) when the Lucene search pipeline fails with an IOException: opening/reusing the IndexReader (ensureAndGetReader), executing searcher.search(), or reading stored fields in extractToolReferences. The library wraps it as a RuntimeException("Failed to search index") because IOException is checked but the search API is not declared to throw.

Solutions

  1. Inspect the wrapped IOException cause to see which step failed (reader open, search, stored-fields fetch)
  2. Verify the index directory exists and is readable/writable by the process
  3. Ensure no clearIndex() or close() runs concurrently with search() for the same session
  4. If reader/index is stale or corrupt, re-initialize the session (re-add documents) and search again
  5. Catch the RuntimeException and return an empty ToolSearchResponse as a graceful fallback in the advisor flow

Example fix

// before
ToolSearchResponse resp = index.search(sessionId, "sql query", 5, 0.5f); // throws RuntimeException on IO failure
// after
ToolSearchResponse resp;
try {
    resp = index.search(sessionId, "sql query", 5, 0.5f);
}
catch (RuntimeException e) {
    logger.warn("tool search failed, returning empty result", e);
    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();
}
Defensive patterns

Strategy: fallback

Validate before calling

File idxDir = new File(indexPath);
if (!idxDir.isDirectory() || !Files.isReadable(idxDir.toPath())) {
    throw new IllegalStateException("Index directory missing or unreadable: " + indexPath);
}

Try / catch

ToolSearchResponse resp;
try {
    resp = index.search(sessionId, query, maxResults, minScore);
}
catch (RuntimeException e) {
    if (e.getCause() instanceof java.io.IOException) {
        logger.warn("Lucene search IO failure for session " + sessionId, e);
    }
    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();
}

Prevention

When it happens

Trigger: Calling search() when the session's index directory is unreadable or deleted, the IndexReader was closed by a concurrent clearIndex(), the underlying index files are corrupt, or an I/O error occurs while fetching stored documents for the matched score docs.

Common situations: Index directory removed or remounted read-only between add and search; reader closed by concurrent clearIndex()/close(); corrupted segment files after a crash mid-commit; NFS/network storage dropping the index directory; searching after the index was rebuilt with an incompatible Lucene version.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/9be7fdfdce802124. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java:253

	 * @param queryString the search query
	 * @param maxResults maximum number of results to return
	 * @param minScore minimum score threshold for results
	 * @return list of matching documents
	 */
	private ToolSearchResponse doSearch(SessionIndex sessionIndex, String queryString, int maxResults, float minScore) {
		try {
			IndexSearcher searcher = new IndexSearcher(sessionIndex.ensureAndGetReader());

			Query query = buildQuery(queryString);
			if (query == null) {
				return ToolSearchResponse.builder().build();
			}

			TopDocs results = searcher.search(query, maxResults);
			return this.extractToolReferences(queryString, searcher, results, minScore);
		}
		catch (IOException e) {
			throw new RuntimeException("Failed to search index", e);
		}
	}

	private ToolSearchResponse extractToolReferences(String query, IndexSearcher searcher, TopDocs results,
			float minScore) throws IOException {

		List<ToolReference> foundToolReferences = new ArrayList<>(results.scoreDocs.length);
		StoredFields storedFields = searcher.storedFields();
		for (ScoreDoc scoreDoc : results.scoreDocs) {
			if (logger.isInfoEnabled()) {
				logger.info("Score: " + scoreDoc.score + ", name: "
						+ storedFields.document(scoreDoc.doc).get(FIELD_TOOL_NAME));
			}
			if (scoreDoc.score >= minScore) {
				var doc = storedFields.document(scoreDoc.doc);
				foundToolReferences.add(ToolReference.builder()
					.relevanceScore(scoreDoc.score)
					.toolName(doc.get(FIELD_TOOL_NAME))

View on GitHub (pinned to 98a7beda4f)