Operations

Spring Data Meilisearch exposes its synchronous, template-style APIs through MeilisearchOperations. The interface combines document access and search APIs, and provides entry points to instance-level and index-level operations. Use the MeilisearchOperations interface in application code; see Index and Instance Operations for instance and index management, Settings for settings, and Object Mapping for entity conversion.

Entry Point

MeilisearchConfiguration registers one MeilisearchOperations bean with the names meilisearchOperations and meilisearchTemplate. Inject the interface when you need direct access to Meilisearch-specific operations instead of the repository abstraction.

getMeilisearchConverter() exposes the configured converter; see Object Mapping for its mapping and conversion behavior.

@RestController
class MovieController {

    private final MeilisearchOperations operations;

    MovieController(MeilisearchOperations operations) {
        this.operations = operations;
    }

    @PostMapping("/movies")
    Movie save(@RequestBody Movie movie) {
        return operations.save(movie);
    }
}

Document Operations

DocumentOperations works with one mapped entity type:

  • save(T) and save(List<T>)

  • get(String, Class<T>) and exists(String, Class<?>)

  • multiGet(Class<T>, List<String>) for ID-list retrieval

  • findAll(Class<T>) and findAll(Class<T>, Sort) for document listing

  • delete(String, Class<?>), delete(T), delete(Class<?>, List<String>), delete(List<T>), and deleteAll(Class<?>)

save(…​) writes through Meilisearch’s add-or-replace documents API. The API creates a missing index; replacing an existing document removes fields omitted from the new entity rather than applying a partial update. Document saves do not automatically apply @Setting metadata.

Both unsorted and sorted findAll(…​) use POST /documents/fetch without requesting specific fields. Meilisearch ignores displayedAttributes for document reads (see its regression test). Document listings can therefore return fields excluded from search results; grant documents.get only to callers allowed to read them.

get(…​) returns null when the document id is not present. findAll(Class<T>) retrieves all documents in one document request and holds the result in memory; it is not a Pageable search. A sorted listing also holds the full result in memory. It counts documents first and then fetches up to that count in one request, so concurrent additions between the requests may be absent. Sort properties must be configured as Meilisearch sortable attributes, and sorted document retrieval requires Meilisearch 1.16 or later. Neither document-list method is bounded by the search-only maxTotalHits setting.

Use multiGet(…​) when the ids are already known rather than listing or searching an index:

List<Movie> requested = operations.multiGet(Movie.class, List.of("movie-3", "movie-1", "missing"));

Missing documents are omitted; the remaining results preserve requested order and duplicate ids. The implementation requests document ids in internal batches; callers supply only the ids they want to retrieve. This uses Meilisearch’s native ID-list documents endpoint, which requires Meilisearch 1.14 or later; there is no individual-document lookup fallback for older servers.

Search Operations

SearchOperations provides:

  • long count(Class<?>) for the number of documents in the mapped index. This is a document count, not a query-filtered search count.

  • <T, Q extends BaseQuery> SearchHits<T> search(Q, Class<T>) for a single index.

  • <T, Q extends BaseQuery> SearchHits<T> multiSearch(List<Q>, Class<T>) for several requests in one non-federated multi-search.

  • <T, Q extends BaseQuery> SearchHits<T> multiSearch(List<Q>, MultiSearchFederation, Class<T>) for federated multi-search.

  • SearchHits<FacetHit> facetSearch(FacetQuery, Class<?>) for facet search.

  • <T> SearchHits<T> similarSearch(SimilarQuery, Class<T>) for similar-document search.

BasicQuery accepts Spring Data Pageable and Sort. The default query page is page 0 with 10 hits; a supplied Pageable is translated to Meilisearch’s one-based page and hitsPerPage values. Sort properties must be configured as sortable attributes. Filter expressions likewise require the referenced attributes to be filterable.

BasicQuery query = BasicQuery.builder()
        .withQ("Wonder Woman")
        .withFilter("genres = Action")
        .withPageable(PageRequest.of(0, 20, Sort.by("title")))
        .build();

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

Use IndexQuery to override the mapped index uid for an individual request. All results are mapped to the entity type passed to multiSearch, so the target indexes must return documents compatible with that type.

List<IndexQuery> queries = List.of(
        IndexQuery.builder().withQ("Wonder Woman").withIndexUid("movies").build(),
        IndexQuery.builder().withQ("Wonder Woman").withIndexUid("comics").build());

SearchHits<Movie> combined = operations.multiSearch(queries, Movie.class);

Non-federated multi-search flattens per-query hits into one SearchHits list in query order; it does not return a separate SearchHits object for each query or an aggregate total.

Non-federated multi-search supports each query’s Pageable. Federated multi-search instead takes the SDK’s MultiSearchFederation options for the combined result window. Per-query Pageable settings are not sent in federated mode; set federation limit and offset instead.

MultiSearchFederation federation = new MultiSearchFederation();
federation.setOffset(0);
federation.setLimit(10);

SearchHits<Movie> federated = operations.multiSearch(queries, federation, Movie.class);

Query Types

Spring Data Meilisearch provides these query value types:

  • BasicQuery for standard search.

  • IndexQuery for per-index multi-search requests, including federated requests.

  • FacetQuery for facet search.

  • SimilarQuery for similar-document search.

SimilarQuery requires a source document id and the name of an embedder configured for the target index:

SimilarQuery similarQuery = SimilarQuery.builder()
        .withDocumentId("movie-1")
        .withEmbedder("default")
        .build();

SearchHits<Movie> similar = operations.similarSearch(similarQuery, Movie.class);

Search Result Types

Search methods return SearchHits<T>, which exposes the hit list, request execution duration, and total-hit metadata when the response provides it. Each SearchHit<T> contains the mapped content, Meilisearch processing time and query, optional facet statistics/distribution, and optional federation metadata.

SearchHits#getTotalHitsRelation() identifies whether getTotalHits() contains a total reported by the search response:

  • EQUAL_TO means Meilisearch reported the total matching-hit count.

  • OFF means total-hit metadata is unavailable; getTotalHits() then reflects the number of loaded hits and must not be treated as a total for the full result set.

Facet search, similar search, and multi-search results do not provide an aggregate total through this API. For a single-index search, use total-hit metadata only when its relation is EQUAL_TO.

The maxTotalHits pagination setting limits how many matching hits can be retrieved through paginated search; it does not necessarily cap the reported total. For example, the current implementation can return 10 hits while reporting 11 total matches when maxTotalHits is 10. Repository findAll(Pageable) uses search results and their reported total, while an unpaged repository lookup uses document retrieval. See Pagination Limits for the pagination setting.

Native SDK Access

For regular application work, prefer MeilisearchOperations, its indexOps(…​) and instanceOps() entry points, or repositories. These APIs apply the selected operation’s mapping, result, exception, and blocking/task contract. Do not assume every operation waits or throws on a failed task: for example, document deletion methods report success through a boolean.

MeilisearchTemplate.execute(MeilisearchCallback) is a supported low-level escape hatch for deliberate native SDK work. It is a concrete-template API, not a method on MeilisearchOperations. Inject MeilisearchTemplate only where this boundary is needed. First check the mapped Operations APIs rather than treating callbacks as the default solution for every missing operation; raw access does not make deferred operational/admin APIs part of the module’s contract.

Access path Mapping and results Exceptions Task completion

Operations/repositories

Follow the selected operation’s mapped request/result contract.

Follow that operation’s Spring Data exception or return contract.

Follow that operation’s blocking/task contract.

MeilisearchTemplate.execute(…​)

Callback supplies SDK requests and interprets SDK results; no automatic entity conversion or mapped index selection.

SDK MeilisearchException failures escaping callback execution receive Template translation.

No automatic wait or completed-task status validation.

Direct native client calls

Caller supplies SDK requests and interprets SDK results.

Do not pass through Template exception translation.

Caller manages task submission, waiting, and outcome checks.

The callback translation boundary is precise: an SDK API error with code document_not_found becomes DocumentAccessException; other SDK MeilisearchException failures become UncategorizedMeilisearchException. Other runtime exceptions are not translated by execute(…​). If a callback catches an SDK exception itself, there is no escaping failure for the template to translate. For comparison, mapped get(id, Entity.class) handles a missing document by returning null; a raw callback requesting the same missing document throws DocumentAccessException.

Submission Is Not Completion

Native requests and results remain SDK-coupled. The following example uses the supported SDK’s TaskInfo, Task, and TaskStatus models; it does not change the ordinary operation task-failure exception contract. It accepts caller-provided JSON with an id primary key, not a mapped entity or its annotation settings. For ordinary entity writes, use operations.save(entity) instead.

import java.util.Objects;

import com.meilisearch.sdk.model.Task;
import com.meilisearch.sdk.model.TaskInfo;
import com.meilisearch.sdk.model.TaskStatus;
import io.vanslog.spring.data.meilisearch.client.msc.MeilisearchTemplate;

class NativeDocuments {

    static Task submitAndCheck(MeilisearchTemplate template, String indexUid, String documentJson) {
        TaskInfo submitted = Objects.requireNonNull(
                template.execute(client -> client.index(indexUid).addDocuments(documentJson, "id")));

        // Submission alone does not confirm success. Waiting and retrieval are explicit native SDK calls.
        Task completed = Objects.requireNonNull(template.execute(client -> {
            var index = client.index(indexUid);
            index.waitForTask(submitted.getTaskUid(), 5_000, 50);
            return index.getTask(submitted.getTaskUid());
        }));

        if (completed.getStatus() != TaskStatus.SUCCEEDED) {
            // Application policy, not automatic Template task-status validation.
            throw new IllegalStateException(
                    "Task " + completed.getUid() + " completed with " + completed.getStatus());
        }
        return completed;
    }
}

The example waits synchronously with an explicit timeout and polling interval in milliseconds. SDK failures during submission, waiting, or retrieval cross the callback translation boundary. A wait timeout is not proof of a failed task: the final outcome may still be unknown. After a successful wait, inspect the retrieved status rather than the submission status; failed or canceled outcomes are not made successful by returning normally from waitForTask(…​). The example’s IllegalStateException is an application choice. Use the SDK task’s error details when available to apply the application’s failure policy; avoid including document payloads or credentials in diagnostics.

Callbacks and direct calls do not automatically use MeilisearchConverter, infer entity metadata/index settings, or wrap native results in mapped SearchHits<T>. SDK method signatures, model types, and version compatibility remain part of native access. See Native Client Boundary for the intentionally retained SDK inheritance. This does not promise SDK task models in ordinary operation failure APIs or add a general-purpose task lifecycle API.

Kotlin Extensions

Spring Data Meilisearch provides Kotlin extension functions for blocking operations that otherwise require an entity Class argument. Reified type parameters let Kotlin callers omit explicit ::class.java arguments.

import io.vanslog.spring.data.meilisearch.core.*

val movie = operations.get<Movie>("movie-1")
val hits = operations.search<Movie>(query)
val count = operations.count<Movie>()
val index = operations.indexOps<Movie>()

operations.applySettings<Movie>()

These extensions are thin wrappers over the Java contracts. They do not change blocking execution behavior or add reactive or coroutine repository support.

Repository Results and Search Metadata

Repository methods return mapped entities (or a Page<T> for paged findAll) rather than SearchHit<T> wrappers. Use MeilisearchOperations directly when you need per-hit metadata such as facet statistics or federation details from a manually issued search.