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

Query Methods

Repository query methods can derive Meilisearch filters from method names. The module supports a limited, search-backed finder subset and filter-based count, existence, and delete methods; use MeilisearchOperations for queries that need explicit search composition.

Query Creation

Spring Data parses derived query method names when the repository is created. For example, findByGenreAndRatingGreaterThan(String genre, int rating) requires both predicates to match. The following table lists the supported keywords and the filters they produce.

Table 1. Supported keywords for query methods
Keyword Sample Meilisearch filter

Is, Equals (or no keyword)

findByTitle(String title)

title = "Arrival"

IsNull, IsNotNull

findByGenreIsNull() / findByGenreIsNotNull()

(genre IS NULL OR genre NOT EXISTS) / (genre EXISTS AND genre IS NOT NULL)

Exists

findByGenreExists()

genre EXISTS

In

findByGenreIn(Collection<String> genres)

genre IN ["drama", "science fiction"]

NotIn

findByGenreNotIn(Collection<String> genres)

genre NOT IN ["drama", "science fiction"]

StartingWith

findByTitleStartingWith(String prefix)

title STARTS WITH "Star"

GreaterThan, GreaterThanEqual

findByRatingGreaterThan(int rating) / findByRatingGreaterThanEqual(int rating)

rating > 7 / rating >= 7

LessThan, LessThanEqual

findByRatingLessThan(int rating) / findByRatingLessThanEqual(int rating)

rating < 7 / rating <= 7

Between

findByRatingBetween(int minimum, int maximum)

rating 5 TO 9

True, False

findByAvailableTrue() / findByAvailableFalse()

available = true / available = false

And

findByGenreAndRatingGreaterThan(String genre, int rating)

Both predicates must match (AND)

Or

findByGenreAndRatingGreaterThanOrAvailableTrue(String genre, int rating)

(genre = "drama" AND rating > 7) OR (available = true)

An empty collection passed to an In predicate is rejected at invocation with an IllegalArgumentException that identifies the method and operator. An empty collection passed to NotIn contributes no filter. With Or, each branch must retain at least one effective predicate; a branch consisting only of an empty NotIn is rejected rather than turning the whole query into an unfiltered search or delete.

StartingWith requires a String property marked filterable in Meilisearch and a String parameter. Null or empty prefixes are rejected before searching or deleting. It matches a literal prefix of the field value, not a token anywhere in the text; findByTitleStartingWith("Star") matches Star Trek but not The Star. The filter value is quoted and escaped. The server must run Meilisearch v1.17.0 or newer: STARTS WITH became stable in v1.17.0 and does not require enabling the experimental containsFilter feature. Older servers reject the filter at request time; this module does not emulate it with full-text search or client-side filtering.

For an equality predicate with an @Nullable argument, passing null matches both an explicitly null field and a missing field. IsNull matches those same two states. IsNotNull requires a present, non-null field, while Exists also matches an explicitly null field. The mapping converter omits null-valued properties when saving documents, so a missing field is the usual result of saving a null property.

Count, Existence, and Delete Methods

Derived predicates can also be used with the following methods:

Table 2. Supported filter-based projection methods
Method pattern Return type Behavior

countBy<Property>(…​)

long, Long

Returns the exact number of matching documents using Meilisearch’s documents/fetch API. This count is not capped by the search pagination.maxTotalHits limit. If the predicates produce no effective filter, countBy returns the unfiltered document count.

existsBy<Property>(…​)

boolean, Boolean

Checks for a matching document using search with a limit of one.

deleteBy<Property>(…​), removeBy<Property>(…​)

long, Long, void

Deletes matching documents with Meilisearch’s native filter-delete API and waits for the task to succeed. Numeric return types report the deleted-document count; a failed task or unavailable deletion details fails the invocation instead of returning a count.

These methods use the same supported predicates listed above, including grouped Or branches. Every property used in a predicate must be configured as filterable. Count, existence, and delete methods do not accept a static OrderBy suffix or runtime Sort or Pageable parameters. A delete or remove method must produce a non-empty effective filter; an unfiltered method or predicates that produce no filter are rejected rather than deleting every document.

Method Return Types

A finder method may return one entity, Optional<Entity>, List<Entity>, Iterable<Entity>, or Page<Entity>. Entity and Optional methods require at most one match. When no hit matches, an entity method returns null by default; if its repository package uses Spring’s @NonNullApi, an unannotated entity method instead throws EmptyResultDataAccessException, while an explicitly @Nullable method may return null. Optional returns the sole hit or Optional.empty(). Either single-result form throws IncorrectResultSizeDataAccessException if multiple hits match. List and Iterable contain matching entity content.

Sorting and Paging

For finder methods, static ordering uses an OrderBy suffix, for example findByGenreOrderByRatingDesc(String genre). A Sort parameter provides runtime ordering, and a Pageable parameter provides an offset, limit, and optional sort. A Page contains the requested window and exposes the total reported by Meilisearch. Without a Pageable, List and Iterable methods fetch all matching hits in chunks and require an exact total; they fail rather than return a partial collection if the index’s pagination.maxTotalHits cap (1,000 by default) prevents complete retrieval. Configure @Pagination(maxTotalHits = …​) when the application needs a different search limit.

Index Settings

String arguments are values, not filter expressions. They are emitted as quoted and escaped Meilisearch filter literals; do not add quotes or filter syntax to the argument. Numeric and boolean values are emitted as literals. Every field used in a predicate—including predicates for count, existence, and delete methods—must be configured as filterable, and every field used for sorting must be configured as sortable. For example:

Nested property paths use dotted Meilisearch field names. For example, findByDetailsDirector(String director) filters details.director = "Director" for an entity with a details.director property. The segments are the mapped Java field names; custom aliases for these fields are not supported. Configure details.director as a filterable attribute; a filter on the top-level details attribute does not substitute for that nested path.

A collection of nested objects can also be filtered by a nested field. For example, findByCreditsDirector("Director") filters credits.director and matches a document with at least one matching object in its credits array. Configure credits.director as filterable. If the path is missing, IsNull matches it under the same missing-field semantics described above. Predicates on different nested fields are combined as document-level filters; they do not require values to come from the same array element.

@Setting(
    filterableAttributes = { "genre", "rating", "available" },
    sortableAttributes = { "rating" }
)
@Document(indexUid = "movies")
class Movie {
  @Id String id;
  String genre;
  int rating;
  boolean available;
}

interface MovieRepository extends MeilisearchRepository<Movie, String> {

  Page<Movie> findByGenreInAndRatingGreaterThanEqual(
      Collection<String> genres, int rating, Pageable pageable);
}

Those index settings must be applied to Meilisearch before the search runs. See Settings for annotation and programmatic configuration.

Query Lookup Strategies

Repository configuration continues to expose queryLookupStrategy and namedQueriesLocation for Spring Data compatibility. Use CREATE or CREATE_IF_NOT_FOUND for derived methods; USE_DECLARED_QUERY cannot resolve them, and declared or named queries are rejected in all lookup modes. See Repository Configuration.

Unsupported Query Methods

The supported predicate and return-type matrix is intentionally narrow. The repository rejects unsupported derived methods during bootstrap, with the method and unsupported operator or return type in the error. In particular:

  • Containing, EndingWith, Like, and Regex are unsupported text-pattern predicates. Containing would require the experimental CONTAINS filter, which is not enabled by this module. Full-text search through MeilisearchOperations with a BasicQuery is a different, tokenized search mode, not an equivalent literal field-prefix or substring filter.

  • Filter-based count, existence, and delete methods are supported only through the patterns and return types listed above; they do not accept sorting or paging. The standard repository count() and delete methods are separate operations and are unchanged.

  • Methods annotated with repository @Query and named-query-backed methods are not supported.

  • Interface or class projections, Slice, streams, scalar-valued finder results, and return types outside the supported method matrix are not supported.

For search modes outside this subset, use MeilisearchOperations directly; it exposes explicit Meilisearch query construction without bypassing the operations abstraction.

Using MeilisearchOperations for Custom Queries

Use MeilisearchRepository for the built-in CRUD, ID lookup, collection listing, sorting, pageable methods, and the supported derived finder, count, existence, and filter-delete methods. Use MeilisearchOperations for search features that need an explicit Meilisearch query or richer search result data, such as facets, similar-document search, or federated multi-search.

For a mapped domain type such as Movie, inject MeilisearchOperations when you need to compose a search query directly:

import io.vanslog.spring.data.meilisearch.core.MeilisearchOperations;
import io.vanslog.spring.data.meilisearch.core.SearchHits;
import io.vanslog.spring.data.meilisearch.core.query.BasicQuery;

class MovieSearchService {

  private final MeilisearchOperations operations;

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

  SearchHits<Movie> search(String text) {
    BasicQuery query = BasicQuery.builder().withQ(text).build();
    return operations.search(query, Movie.class);
  }
}

This service-level call returns Meilisearch search hits. Repository derived queries provide a small, filter-oriented subset for common lookups; use MeilisearchOperations when search needs free-text ranking, facets, similar-document search, or federated multi-search.

Base Repository Paging, Sorting, and Result Limits

Derived finder and existence methods use search. Derived count methods use documents/fetch, and derived filter-delete methods use the native index filter-delete API. The standard repository methods have separate behavior:

  • A paged findAll(Pageable) is search-backed. Its Page total comes from the total reported by Meilisearch search results. pagination.maxTotalHits can cap returned content without capping that reported total.

  • An unpaged findAll(Pageable) uses the document-list API, retrieves all documents into memory, and sets the Page total to the retrieved count. It is not limited by maxTotalHits.

  • findAll() also lists all documents through the document API. Sorted findAll(Sort) and sorted unpaged findAll(Pageable) count first, then fetch up to that count; additions between those requests may be absent.

  • Sorting document listings requires a sortable index attribute and Meilisearch 1.16 or later. findAllById(…​) uses native ID-list retrieval, available from Meilisearch 1.14, with no fallback on older servers.

  • Document-list reads are not capped by search maxTotalHits and ignore displayedAttributes.

  • Empty saveAll(…​), deleteAll(Iterable), and deleteAllById(…​) operations complete without a server request. The no-argument deleteAll() still deletes all documents for the entity type.

For full method-to-API behavior, see Method Behavior at a Glance.