Search API deprecations and upgrade guide for Jira 11

To offer more flexible options for search tools, we're introducing an abstraction layer to Jira's search functionality. This change will support OpenSearch by removing references to Lucene from public APIs. Despite these changes, search and indexing performance will remain consistent with the current Lucene implementation, ensuring a smooth transition.

This page outlines the Lucene-specific APIs and components that are being removed and provides details on how to migrate to the platform-agnostic search API. 

We are actively working on the upgrade documentation and will continue to update this article. Additionally, we will communicate any changes in our changelog to make tracking them easier. Explore the changelog

On this page:

Migrating breaking API changes to Search API

Due to the length of some of the strings in the table below, you might not see the full table width. To see more to the right or left, hover your mouse over the table before using it to scroll.

Module

Deprecation

Instructions

jira-api

com.atlassian.jira.index.EntitySearchExtractor and its subtypes:

  1. com.atlassian.jira.index.ChangeHistorySearchExtractor
  2. com.atlassian.jira.index.CommentSearchExtractor
  3. com.atlassian.jira.index.IssueSearchExtractor

Use com.atlassian.jira.search.entity.EntityIndexExtractor and its subtypes instead:

  1. com.atlassian.jira.search.entity.ChangeHistoryIndexExtractor
  2. com.atlassian.jira.search.entity.CommentIndexExtractor
  3. com.atlassian.jira.search.entity.IssueIndexExtractor
  4. com.atlassian.jira.search.entity.WorkLogIndexExtractor

jira-api

com.atlassian.jira.issue#getIssue(org.apache.lucene.document.Document issueDocument)

Use com.atlassian.jira.issue#getIssue(com.atlassian.jira.search.Document issueDocument)

jira-api

com.atlassian.jira.jql.query.ClauseQueryFactory and related interfaces:


  1. com.atlassian.jira.jql.query.QueryFactoryResult
  2. com.atlassian.jira.jql.query.OperatorSpecificQueryFactory
  3. com.atlassian.jira.issue.customfields.searchers.CustomFieldSearcherClauseHandler#getClauseQueryFactory() and all implementations


Use com.atlassian.jira.search.jql.ClauseQueryMapper instead, and the corresponding interfaces:

  1. com.atlassian.jira.search.Query
  2. com.atlassian.jira.search.jql.OperatorSpecificQueryFactory
  3. Instead, implement com.atlassian.jira.issue.customfields.searchers.CustomFieldSearcherClauseHandler#getClauseQueryMapper()

jira-api

com.atlassian.jira.issue.search.LuceneFieldSorter

Classes extending LuceneFieldSorterTextFieldSorter

Also deprecated, related searcher SPIs:

  1. com.atlassian.jira.issue.customfields.MultiSortableCustomFieldSearcher
  2. com.atlassian.jira.issue.customfields.SortableCustomFieldSearcher
  3. com.atlassian.jira.issue.customfields.NaturallyOrderedCustomFieldSearcher#getSortFieldType()

The deprecated class is used for two use cases: sorting, and loading field values from the index. Depending on the use case, replace LuceneFieldSorter with one of the following:

  • com.atlassian.jira.search.FieldSort
  • com.atlassian.jira.search.FieldValueLoader

Migrate related searcher SPIs to the corresponding replacements:

  1. com.atlassian.jira.issue.customfields.FieldSortsCustomFieldSearcher
  2. com.atlassian.jira.issue.customfields.ValueLoaderCustomFieldSearcher
  3. Remove implementations of com.atlassian.jira.issue.customfields.NaturallyOrderedCustomFieldSearcher#getSortFieldType().
    For example, revert to default.

lucene

  • org.apache.lucene.search.SortField
    • For example, com.atlassian.jira.issue.fields.NavigableField#getSortFields()
  • org.apache.lucene.search.FieldComparatorSource
    • For example, com.atlassian.jira.issue.fields.NavigableField#getSortComparatorSource()

Use com.atlassian.jira.search.FieldSort instead.

Use one of the subtypes that corresponds with the legacy FieldComparatorSource:

  • NaturalFieldSort: replaces StringSortComparatorSource and LongSortComparatorSource
  • ValueLoaderFieldSort: replaces MappedSortComparator
  • ScoreComputedFieldSort: replaces UserHistoryFieldComparatorSource

jira-api

  • com.atlassian.jira.issue.index.indexers.FieldIndexer
  • com.atlassian.jira.issue.index.indexers.CustomFieldIndexer

Base classes:

  • com.atlassian.jira.issue.index.indexers.impl.BaseFieldIndexer
  • com.atlassian.jira.issue.index.indexers.impl.AbstractCustomFieldIndexer

Indexer implementations in com.atlassian.jira.issue.index.indexers.impl package:

  • ExactTextCustomFieldIndexer
  • LocalDateIndexer
  • NumberCustomFieldIndexer
  • UserCustomFieldIndexer
  • VersionCustomFieldIndexer

Deprecated indexers are registered into Jira by the following SPI which is also deprecated:
com.atlassian.jira.issue.search.searchers.information.SearcherInformation#getRelatedIndexers()

  • GenericSearcherInformation constructor accepting FieldIndexer.
  • Utility class com.atlassian.jira.issue.index.indexers.impl.FieldIndexerUtil is Lucene-specific and will be removed.

Implement com.atlassian.jira.search.issue.index.indexers.FieldIndexer or com.atlassian.jira.search.issue.index.indexers.CustomFieldIndexer instead, or extend one of its base classes:

  • com.atlassian.jira.search.issue.index.indexers.impl.VisibilityBaseFieldIndexer
  • com.atlassian.jira.search.issue.index.indexers.impl.BaseCustomFieldIndexer

Use indexer implementations in the com.atlassian.jira.search.issue.index.indexers.impl package

Use the following SPI to register new indexers:
com.atlassian.jira.issue.search.searchers.information.SearcherInformation#getIndexers()

Use com.atlassian.jira.issue.search.searchers.information.GenericSearcherInformation.Builder to coinstruct a SearchInformation instance.

lucene

org.apache.lucene.document.Document

To read indexed documents, use com.atlassian.jira.search.Document instead.

To index into a document, use com.atlassian.jira.search.field.FieldValueCollector instead.

Also see:

lucene

org.apache.lucene.search.Query

Use com.atlassian.jira.search.Query instead.

Note that in the deprecated implementation, an empty query would result in match none query. In the new Search API to match OpenSearch, an empty query isn't a match all query.

An empty query can be replaced with DefaultMatchNoDocsQuery.INSTANCE

jira-api

DocumentConstants#LUCENE_SORTFIELD_PREFIX

Use #SORTFIELD_PREFIX instead.

jira-api

com.atlassian.jira.issue.index.SecurityIndexingUtils methods that return a org.apache.lucene.util.ByteRef:

  • generateProjectPermissionFieldContents(...)
  • generateIssueLevelPermissionContents(...)

Use these following methods that return a String instead:

  • generateProjectPermissionFieldContentString(...)
  • generateIssueLevelPermissionContentString(...)

jira-core

com.atlassian.jira.index.EntityDocumentFactory and its subtypes:

  1. com.atlassian.jira.issue.index.ChangeHistoryDocumentFactory
  2. com.atlassian.jira.issue.index.CommentDocumentFactory
  3. com.atlassian.jira.issue.index.IssueDocumentFactory
  4. com.atlassian.jira.issue.index.WorklogDocumentFactory

Use these interfaces instead:

  1. com.atlassian.jira.search.issue.index.ChangeHistoryFieldValuesFactory
  2. com.atlassian.jira.search.issue.index.CommentFieldValuesFactory (will come in a future version)
  3. com.atlassian.jira.search.issue.index.IssueFieldValuesFactory
  4. com.atlassian.jira.search.issue.index.WorklogFieldValuesFactory (will come in a future version)

lucene

org.apache.lucene.search.IndexSearcher, including its subtypes:

  • com.atlassian.jira.index.ManagedIndexSearcher
  • com.atlassian.jira.index.UnmanagedIndexSearcher
  • com.atlassian.jira.index.DelayCloseSearcher

Use com.atlassian.jira.search.index.IndexSearcher instead.

jira-lucene-dmz

com.atlassian.jira.issue.search.SearchProvider

Use com.atlassian.jira.search.issue.IssueDocumentSearchService instead.

jira-lucene-dmz

com.atlassian.jira.issue.search.SearchProviderFactory

Use com.atlassian.jira.search.index.IndexAccessorRegistry

  • If searching for issues, use getIssuesIndexAccessor to get IndexAccessor
  • Otherwise, use getOrCreate(final String indexName) to get IndexAccessor

Then get IndexSearcher from IndexAccessor

jira-lucene-dmz

com.atlassian.jira.issue.search.parameters.lucene.JiraBytesRef

No replacement. These methods are Lucene-specific, not relevant on OpenSearch going forward.

jira-lucene-dmz

com.atlassian.jira.issue.search.parameters.lucene.sort.JiraLuceneFieldFinder

No replacement. These methods are Lucene-specific, not relevant on OpenSearch going forward.

jira-lucene-dmz

com.atlassian.jira.issue.search.SearchQuery

Use com.atlassian.jira.search.request.SearchRequest instead for document based search, or com.atlassian.jira.issue.search.SearchRequest for issue search.

jira-lucene-dmz

com.atlassian.jira.issue.index.IssueIndexManager

Use com.atlassian.jira.search.index.IndexAccessor or com.atlassian.jira.issue.index.IssueIndexer instead.

jira-lucene-dmz

com.atlassian.jira.issue.index.TemporaryIndexProvider

This class only supports Lucene index which is deprecated. No replacements to support other search platforms such as OpenSearch.

lucene

org.apache.lucene.search.Collector

Explore our guide to migrate Lucene collectors

REST API

PUT: /api/2/cluster/index-snapshot/{nodeId}

Explore our REST API documentation

No replacements. This endpoint is Lucene-specific, not relevant for the Search API or OpenSearch going forward.

jira-api

com.atlassian.jira.issue.customfields.SortableCustomField

com.atlassian.jira.issue.fields.CustomField#compare()

The deprecated SortableCustomField interface is used to provide a comparator for sorting issues. It is used as a fallback when the Custom Field isn't associated with a Custom Field Searcher that supports sorting.

A custom field already using a Custom Field Searcher with sorting can remove the SortableCustomField with no additional changes required. A custom field that relies on SortableCustomField for sorting will need to implement a Custom Field Searcher with sorting and associate it with the custom field type.

The equivalent Custom Field Searcher implementation should:

  • implement FieldSortsCustomFieldSearcher
  • in getFieldSorts(), return a DefaultValueLoaderFieldSort
  • pass a FieldValueLoader to the DefaultValueLoaderFieldSort that implements getComparator() with the same comparison logic used by the deprecated SortableCustomField#Compare

Note that where possible, other more efficient sort types such as NaturalFieldSort should be used over ValueLoaderFieldSort.

For a multi-value field, SortableCustomField could implement a comparator that uses the full list of field values for that issue. In contrast, ValueLoaderFieldSort can only compare a single value. When an issue has multiple values for the field, the smallest value (based on DefaultValueLoaderFieldSort#getComparator()) will be used to compare that issue against others.

jira-core

com.atlassian.jira.issue.fields.ImmutableCustomField#compare()

com.atlassian.jira.issue.fields.ImmutableCustomField#getSortComparatorSource()

See the migration notes above for com.atlassian.jira.issue.customfields.SortableCustomField

jira-api

com.atlassian.jira.issue.statistics.StatisticsMapper

  • getDocumentConstant()
  • getValueFromLuceneField()

Use methods from the FieldValueLoader interface:

  • field()
  • loadValue()

jira-lucene-dmz

com.atlassian.jira.web.bean.StatisticAccessorBean

com.atlassian.jira.web.bean.StatisticMapWrapper

Use Search API replacements from the jira-api module:

  • com.atlassian.jira.issue.statistics.StatisticsMapGenerator
  • com.atlassian.jira.issue.statistics.StatisticMapWrapper

jira-api

com.atlassian.jira.search.jql.ClauseQueryMapperToFactoryAdapter

This class is a temporary helper added in Jira 10.4 to facilitate migrating to Search API. There is no replacement in Jira 11.

jira-api

com.atlassian.jira.plugin.versionpanel.impl.GenericTabPanel

Constructors which have SearchProvider as a parameter.

Use the constructor which doesn't have SearchProvider as a parameter

lucene

com.atlassian.jira.index.stats.IndexSearcherWithStats

Use com.atlassian.jira.search.index.stats.IndexSearcherWithStats instead.

Date field indexing

The LocalDate is now stored as milliseconds since the epoch instead of days since the epoch. This change affects the following fields: due date, work log date, and custom date picker. However, this update will be seamless for data access through the Search API.

When installing Jira 11, re-indexing will be necessary. If you’re indexing dates, ensure that you index them as milliseconds since the epoch.

Query total document count

To query the total number of documents in the index, you can use the existing Lucene API. Here's how you can retrieve the total number of issues:

// Deprecated searchProviderFactory.getSearcher(SearchProviderFactory.ISSUE_INDEX).getIndexReader().numDocs()

For counting documents, the Search API service provides the IssueDocumentSearchService.getHitCount() method. However, Jira has a configuration option that can force the return of no matches for an empty JQL query:

String JIRA_EMPTY_JQL_RETURNS_NO_DATA_ENABLED = "jira.empty.jql.returns.no.data.enabled"

When this option is enabled, getHitCount will parse an empty JQL to return 0.

Here are alternatives that return the correct document count regardless of the jira.empty.jql.returns.no.data.enabled configuration setting:

  • To use this option, autowire IssueIndexAccessorRegistry as a dependency, then execute the following:
issueIndexAccessorRegistry.getIssuesIndexAccessor().getSearcher() .getHitCount(SearchRequest.builder().query(DefaultMatchAllDocsQuery.INSTANCE).documentType(DocumentTypes.ISSUE).build(), timeout);
  • If the result needs to be filtered by a permission filter based on the current ApplicationUser, follow these steps:
    1. Autowire DefaultQueryFactory as a dependency.

    2. Use an empty JQL query with a filter query set to DefaultMatchAllDocsQuery.INSTANCE.

      var query = defaultQueryFactory.createIssueQuery(applicationUser, jqlQuery, DefaultMatchAllDocsQuery.INSTANCE, overrideSecurity);
      issueIndexAccessorRegistry.getIssuesIndexAccessor().getSearcher()
        .getHitCount(SearchRequest.builder().query(query).documentType(DocumentTypes.ISSUE).build(), timeout);

Preparing for OpenSearch compatibility

While the Search API provides a unified interface that allows code to compile and function seamlessly with both Lucene and OpenSearch, it's important to recognize that there are fundamental differences between these two underlying search platforms, especially related to scale and performance. Even when your plugin code compiles against the Search API and works as expected with Lucene, certain operations may be subject to new limitations when running on OpenSearch.

Search results size limit

Lucene has no limit on the number of documents that can be returned from a single search request, but OpenSearch does. The default is 10,000, and it is configurable via the max_result_window setting in the OpenSearch index.

Explore the related OpenSearch documentation:

It's recommended to design your application so that retrieving an unlimited or very large number of documents is not necessary. Some techniques include using pagination or the aggregation API.

If it is unavoidable, the searchStream API provides a safe way to stream a large volume of results regardless of the search platform.

Terms query size limit

com.atlassian.jira.search.query.TermSetQuery is the Search API equivalent of Lucene’s TermsQuery.

Unlike Lucene, OpenSearch enforces a limit on the number of terms that a Terms Query can contain. This limit is configurable via the max_terms_count setting and defaults to 65,536.

Explore the related OpenSearch documentation:


Last modified on Jun 4, 2025

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.