This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data Meilisearch 0.9.0!

Meilisearch Operations

Spring Data Meilisearch uses several interfaces to define the operations that can be called against a Meilisearch index.

  • DocumentOperations defines actions to store, update and retrieve entities based on their id.

  • SearchOperations defines the actions to search for multiple entities using queries.

  • MeilisearchInstanceOperations defines instance-level actions such as health, version, and stats.

  • MeilisearchIndexOperations defines actions scoped to a single index.

  • MeilisearchOperations combines the DocumentOperations and SearchOperations interfaces and exposes accessors for instance and index operations.

These interfaces correspond to the structuring of the Meilisearch API.

The default implementations of the interfaces offer:

  • Read/Write mapping support for domain types

  • A rich query API

  • Resource management and exception translation

Usage examples

The example shows how to use an injected MeilisearchOperations instance in a Spring REST controller. The example assumes that Movie is a class that is annotated with @Document, @Id etc.

Example 1. MeilisearchOperations usage
@RestController
@RequestMapping("/")
public class MovieController {

  private MeilisearchOperations meilisearchOperations;

  public MovieController(MeilisearchOperations meilisearchOperations) { (1)
    this.meilisearchOperations = meilisearchOperations;
  }

  @PostMapping("/movie")
  public String save(@RequestBody Movie movie) {                        (2)
    Movie savedEntity = meilisearchOperations.save(movie);
    return savedEntity.getId();
  }

  @GetMapping("/movie/{id}")
  public Movie findById(@PathVariable("id") String id) {               (3)
    return meilisearchOperations.get(id, Movie.class);
  }
}
1 Let Spring inject the provided MeilisearchOperations bean in the constructor.
2 Store a movie in the Meilisearch instance.
3 Retrieve the movie by id.

Instance and Index Operations

MeilisearchOperations exposes concern-specific operations for APIs that are not document or search operations. Use instanceOps() for Meilisearch instance-level information and indexOps(…​) for lifecycle and statistics operations bound to one index. Version, stats, and index lifecycle operations use the configured Meilisearch API key, so server-side key permissions and index restrictions still apply.

Example 2. Instance and index operation usage
MeilisearchHealth health = meilisearchOperations.instanceOps().health();
boolean healthy = meilisearchOperations.instanceOps().isHealthy();
MeilisearchVersion version = meilisearchOperations.instanceOps().version();
MeilisearchStats stats = meilisearchOperations.instanceOps().stats();

MeilisearchIndexStats movieStats = meilisearchOperations.indexOps(Movie.class).stats(); (1)
MeilisearchIndexStats namedStats = meilisearchOperations.indexOps("movies").stats(); (2)

MeilisearchIndexOperations catalogIndex = meilisearchOperations.indexOps("catalog");
MeilisearchIndex created = catalogIndex.create();
MeilisearchIndex updated = catalogIndex.update(new MeilisearchIndexUpdateRequest("id"));
MeilisearchIndex loaded = catalogIndex.get();
MeilisearchIndexList indexes = catalogIndex.list(new MeilisearchIndexQuery(0, 100));
MeilisearchIndexSettings settings = catalogIndex.getSettings();
MeilisearchIndexSettings updatedSettings = catalogIndex.updateSettings(
    MeilisearchIndexSettings.builder()
        .withSearchableAttributes(List.of("title", "description"))
        .withFilterableAttributes(List.of("genres"))
        .withPagination(new MeilisearchIndexSettings.PaginationSettings(1500))
        .build());
MeilisearchIndexSettings resetSettings = catalogIndex.resetSettings();
boolean deleted = catalogIndex.delete();
1 Resolve the index uid from the mapping metadata of the @Document entity class.
2 Bind operations directly to a non-empty index uid.

indexOps(Class<?>) resolves the index uid from the entity mapping, so indexOps(Movie.class) targets the same @Document(indexUid = "movies") index used by template and repository operations. indexOps(String) is available when the index uid is not represented by a mapped entity class.

The index lifecycle API returns Spring Data Meilisearch-owned request and response types such as MeilisearchIndex, MeilisearchIndexCreateRequest, MeilisearchIndexUpdateRequest, MeilisearchIndexQuery, and MeilisearchIndexList. It does not expose Meilisearch Java SDK model types from public operation signatures. Runtime settings APIs follow the same rule: MeilisearchIndexSettings and its nested value types are Spring Data Meilisearch-owned and no com.meilisearch.sdk.model.Settings types are exposed.

Meilisearch performs create, update, and delete operations asynchronously. The default template waits for the submitted task to complete using the configured request timeout and retry interval before returning from lifecycle methods and from settings update/reset methods.

list(…​) returns a page of indexes for the configured key; it is exposed from indexOps(…​) to keep index management under the index operations API rather than introducing a separate admin wrapper. Updating an index currently covers the practical Meilisearch lifecycle update exposed for a bound index: assigning the primary key, typically before documents are added. Runtime settings management is exposed on the same index-scoped operations object through getSettings(), updateSettings(…​), and resetSettings(). updateSettings(…​) sends the settings fields present in the provided MeilisearchIndexSettings value to Meilisearch; use empty lists or maps to clear supported list/map settings and use resetSettings() to restore all settings to Meilisearch defaults. Do not rely on null values to reset individual settings. Annotation-driven applySettings(…​) remains available for applying settings declared on mapped entity classes and is separate from ad-hoc runtime settings updates.

The API key must be allowed to perform the requested index actions. For restricted keys, create, retrieve/list, update, and delete operations require the corresponding Meilisearch index permissions (for example indexes.create, indexes.get, indexes.update, and indexes.delete) and must also match the key’s index restrictions. Deleting or renaming an index outside the mapping does not change @Document(indexUid = …​); repositories continue to target the index uid declared on the entity.

Search Result Types

When searching with the methods of the SearchOperations interface, additional information is available for each entity.

The following classes and interfaces are available:

SearchHit<T>

Contains the following information:

  • The retrieved entity of type <T> (content)

  • Processing time in milliseconds (processingTimeMs)

  • The search query string (query)

  • Facet statistics if available (facetStats)

  • Facet distribution information if available (facetDistribution)

  • Federation information for multi-search operations if available (federation)

SearchHits<T>

Contains the following information:

  • An unmodifiable list of SearchHit<T> objects

  • Total hit metadata through getTotalHits() and getTotalHitsRelation()

    • EQUAL_TO: getTotalHits() is equal to the total matching result set reported by the search engine.

    • OFF: exact total-hit metadata is unavailable, so getTotalHits() is the loaded hit count and is not usable as a page total.

  • Execution duration

  • Methods to access individual search hits and check if results exist

The number of returned hits is separate from the reported total hits. Use getSearchHits().size() for the current result content and use getTotalHits() as exact search total metadata only when getTotalHitsRelation() is EQUAL_TO. Meilisearch exposes this exact total through paginated search responses; estimated totals and flattened or aggregate responses are reported with OFF.

Queries

The SearchOperations interface methods take different Query parameters that define the query to execute. Spring Data Meilisearch provides several implementations: BasicQuery, IndexQuery, FacetQuery, and SimilarQuery.

BasicQuery

BasicQuery is the standard implementation for simple search queries:

Example 3. Search for movies with a given title
BasicQuery query = new BasicQuery("Wonder Woman");

IndexQuery

IndexQuery is used for multi-search operations:

List<IndexQuery> queries = List.of(
    new IndexQuery("Wonder Woman"),
    new IndexQuery("Batman")
);

FacetQuery

FacetQuery is used for faceted search operations:

FacetQuery query = FacetQuery.builder()
    .withFacetName("genres")
    .withFacetQuery("act")
    .build();

SimilarQuery

SimilarQuery is used for similar documents search operations. It requires the source document id and the embedder name configured in Meilisearch:

SimilarQuery query = SimilarQuery.builder()
    .withDocumentId("143")
    .withEmbedder("manual")
    .withLimit(10)
    .build();

Search Types

Spring Data Meilisearch supports different types of search operations to accommodate various use cases.

Standard search allows you to search for documents within a single index:

// Search with BasicQuery
BaseQuery query = new BasicQuery("Wonder Woman");
SearchHits<Movie> result = meilisearchOperations.search(query, Movie.class);
List<SearchHit<Movie>> searchHits = result.getSearchHits();
List<Movie> movies = searchHits.stream().map(SearchHit::getContent).toList();

// Search with filter
BaseQuery filterQuery = BasicQuery.builder()
    .withFilter(new String[] { "genres = Drama" })
    .build();
SearchHits<Movie> filterResult = meilisearchOperations.search(filterQuery, Movie.class);

Spring Data Meilisearch supports two types of multi-search operations: non-federated and federated multi-search.

Non-federated multi-search allows you to execute multiple search queries in a single request. This can be done with homogeneous query types, mixed query types, or across multiple indices:

// Multi-search across single indices
List<BaseQuery> queries = List.of(
    new BasicQuery("Carol"),
    new BasicQuery("Wonder Woman")
);
SearchHits<Movie> singleIndexResults = meilisearchOperations.multiSearch(queries, Movie.class);

// Multi-search across multiple indices
List<BaseQuery> multiIndexQueries = List.of(
    IndexQuery.builder().withQ("Carol").withIndexUid("movies").build(),
    IndexQuery.builder().withQ("Wonder Woman").withIndexUid("comics").build()
);
SearchHits<Movie> multiIndexResults = meilisearchOperations.multiSearch(multiIndexQueries, Movie.class);

Federated multi-search allows you to combine and process results from multiple indices using different strategies like merging or joining the results. This is particularly useful when you need to search across multiple indices and want to control how the results are combined:

// Configure federation options
MultiSearchFederation federation = new MultiSearchFederation();
federation.setLimit(20);  // Total number of results to return
federation.setOffset(0);  // Starting position for results
federation.setMergeFacets(true);  // Combine facets from all indices

// Perform federated search across multiple indices
List<BaseQuery> multiIndexQueries = List.of(
    IndexQuery.builder().withQ("Wonder Woman").withIndexUid("movies").build(),
    IndexQuery.builder().withQ("Wonder Woman").withIndexUid("comics").build()
);
SearchHits<Movie> federatedResults = meilisearchOperations.multiSearch(multiIndexQueries, federation, Movie.class);
The pageable option in query is not supported in federated multi-search. Instead, use the limit and offset parameters in the federation configuration.

While both BasicQuery and IndexQuery can be used for multi-search operations, IndexQuery is required when you need to specify different index UIDs or configure federation options. Only IndexQuery provides methods like withIndexUid() that allow you to search across multiple indices.

When performing multi-index searches, all results are converted to the specified class type (Movie.class in the example). If you need to preserve the original types, you should extract and compare specific fields rather than entire objects.

Facet search allows you to retrieve facet information for building faceted navigation interfaces:

// First, ensure the field is set as filterable in the index settings
meilisearchOperations.applySettings(Movie.class);

// Perform facet search
FacetQuery query = new FacetQuery("genres");
SearchHits<FacetHit> result = meilisearchOperations.facetSearch(query, Movie.class);

// Process facet results
result.getSearchHits().forEach(hit -> {
    FacetHit facetHit = hit.getContent();
    System.out.println("Value: " + facetHit.getValue());
    System.out.println("Count: " + facetHit.getCount());
});

Unlike standard and multi-search operations that return entities of type T, facet searches return FacetHit objects containing facet information. Note that while the clazz parameter still specifies the document type being searched, the return type is always SearchHits<FacetHit> rather than SearchHits<T>.

Similar documents search uses Meilisearch’s Similar Documents API to retrieve documents that are semantically close to a source document. The index must already have a compatible embedder configured in Meilisearch before executing this operation.

SimilarQuery query = SimilarQuery.builder()
    .withDocumentId("143")
    .withEmbedder("manual")
    .withAttributesToRetrieve(new String[] { "title", "description" })
    .withFilter("genres = Action")
    .withLimit(10)
    .withShowRankingScore(true)
    .build();

SearchHits<Movie> result = meilisearchOperations.similarSearch(query, Movie.class);
List<Movie> movies = result.getSearchHits().stream()
    .map(SearchHit::getContent)
    .toList();

documentId and embedder are required. Optional parameters such as limit, offset, filter, ranking-score flags, and vector retrieval are only sent to Meilisearch when explicitly configured on SimilarQuery.