|
This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data Meilisearch 0.13.0! |
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)andsave(List<T>) -
get(String, Class<T>)andexists(String, Class<?>) -
multiGet(Class<T>, List<String>)for ID-list retrieval -
findAll(Class<T>)andfindAll(Class<T>, Sort)for document listing -
delete(String, Class<?>),delete(T),delete(Class<?>, List<String>),delete(List<T>), anddeleteAll(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();
Multi-Search
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:
-
BasicQueryfor standard search. -
IndexQueryfor per-index multi-search requests, including federated requests. -
FacetQueryfor facet search. -
SimilarQueryfor 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 aggregate 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.
For a blocking single-index search, SearchHits#getResponseMetadata() retains one SearchResponseMetadata entry even
when the hit list is empty. It exposes the response’s processing time, query, pagination fields, and facet
distribution/statistics independently of the returned hits. SearchHits#hasResponseMetadata() distinguishes captured
metadata from an operation that did not capture it.
SearchHits#getTotalHitsRelation() identifies whether getTotalHits() contains an exact total reported by the search
response:
-
EQUAL_TOmeans Meilisearch reported the total matching-hit count. -
OFFmeans aggregate 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.
SearchResponseMetadata#getTotalHitsRelation() separately reports EQUAL_TO, ESTIMATED, or OFF for each
response. Exact and estimated response totals remain separate fields. For a single-index response that reports an exact
totalHits, that value also supplies the aggregate SearchHits total with relation EQUAL_TO. An estimate alone does
not establish an exact aggregate total; the aggregate remains the loaded-hit count with relation OFF.
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. |
|
Callback supplies SDK requests and interprets SDK results; no automatic entity conversion or mapped index selection. |
SDK |
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.