© 2023-2025 The original author(s).
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. |
Preface
1. Project Metadata
-
Version Control - https://github.com/junghoon-vans/spring-data-meilisearch
-
Bugtracker - https://github.com/junghoon-vans/spring-data-meilisearch/issues
-
Release repository - https://s01.oss.sonatype.org/content/repositories/releases/
-
Snapshot repository - https://s01.oss.sonatype.org/content/repositories/snapshots/
2. Requirements
Requires an installation of Meilisearch.
Reference Documentation
3. Meilisearch Client
This chapter describes configuration and usage of the Meilisearch client.
Spring Data Meilisearch operates upon a Meilisearch Client
(provided by Meilisearch Java) that is connected to a single Meilisearch instance.
3.1. Annotation based configuration
The Spring Data Meilisearch client can be configured using the @Configuration
annotation.
@Configuration
public class MyClientConfig extends MeilisearchConfiguration {
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() (1)
.connectedToLocalhost()
.withApiKey("masterKey")
.build();
}
}
1 | for a detailed description of the builder methods see Client Configuration |
3.2. Spring Namespace
The Spring Data Meilisearch client can be configured using the Spring XML namespace.
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:meilisearch="http://www.vanslog.io/spring/data/meilisearch"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.vanslog.io/spring/data/meilisearch
http://www.vanslog.io/spring/data/meilisearch/spring-meilisearch-1.0.xsd">
<meilisearch:meilisearch-client id="meilisearchClient" api-key="masterKey"/>
</beans>
When setting up a client using the Spring namespace, a JSON handler must be set up. Use the following section, Spring Namespace of JSON handler. |
3.3. Client Configuration
Configuration options are provided by the ClientConfiguration
builder.
String[] agents = {"Spring Data Meilisearch"}; (1)
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("http://localhost:7700") (2)
.withApiKey("masterKey") (3)
.withClientAgents(agents) (4)
.withRequestTimeout(2000) (5)
.withRequestInterval(20) (6)
.build();
1 | Define the client agents that will be sent as a User-Agent header to Meilisearch. |
2 | Use the builder to provide the Meilisearch host URL. |
3 | Set the Meilisearch API Key to use for authentication. |
4 | Set the client agents. |
5 | Set the request timeout to 2000 milliseconds. |
6 | Set the request interval to 20 milliseconds. |
3.4. JSON handler
The JSON handler
is used to serialize and deserialize the JSON data.
3.4.1. Supported JSON handlers
The following JSON handlers are supported:
-
com.meilisearch.sdk.json.GsonJsonhandler
-
com.meilisearch.sdk.json.JacksonJsonhandler
-
Custom JSON handler that implements
com.meilisearch.sdk.json.Jsonhandler
3.4.2. Annotation based configuration
You don’t need to provide a JSON handler if you are using the default JSON handler, GsonJsonhandler
.
But you can override the jsonhandler()
method to provide a custom JSON handler.
@Configuration
public class JsonhandlerConfig extends MeilisearchConfiguration {
@Override
public Jsonhandler jsonhandler() {
return new JacksonJsonhandler(); (1)
}
}
1 | Use Jackson JSON handler instead of the default JSON handler. |
3.4.3. Spring Namespace
The JSON handler must be registered as a bean with the id=jsonhandler
.
And pass the class name of the JSON handler to the class
attribute.
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:meilisearch="http://www.vanslog.io/spring/data/meilisearch"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.vanslog.io/spring/data/meilisearch
http://www.vanslog.io/spring/data/meilisearch/spring-meilisearch-1.0.xsd">
<bean id="jsonhandler" class="com.meilisearch.sdk.json.GsonJsonhandler"/>
</beans>
4. Meilisearch Document
Spring Data Meilisearch provides a simple way to define a Document.
4.1. Document definition
To define the Document, you need to annotate a class with @Document
annotation.
As an example, the following class defines a Movie
document with id
, title
, description
and genres
fields.
@Document(indexUid = "movies")
public class Movie {
@Id private int id;
private String title;
private String description;
private String[] genres;
}
4.2. Index UID
The Index UID is a unique identifier for an index.
It can be defined explicitly with the indexUid
attribute in the @Document
annotation.
@Document(indexUid = "movies")
public class Movie {
// fields
}
If the indexUid
is not specified or the class is not annotated with @Document
, the class name will be used as the index UID by default.
// No @Document annotation or no indexUid specified
public class Movie {
// fields
}
In this case, "Movie" will be used as the index UID
4.3. Document id
The Document id is a unique identifier for a document in an index.
It can be defined with @Id
annotation or id
field.
5. 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
define the actions to search for multiple entities using queries -
MeilisearchOperations
combines theDocumentOperations
andSearchOperations
interfaces.
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
5.1. 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.
@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 with a get by id |
5.2. 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:
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)
Contains the following information:
-
An unmodifiable list of
SearchHit<T>
objects -
Execution duration
-
Methods to access individual search hits and check if results exist
5.3. Queries
The SearchOperations
interface methods take different Query
parameters that define the query to execute.
Spring Data Meilisearch provides several implementations: BasicQuery
, IndexQuery
, and FacetQuery
.
5.3.1. BasicQuery
BasicQuery
is the standard implementation for simple search queries:
BasicQuery query = BasicQuery("Wonder Woman");
5.4. Search Types
Spring Data Meilisearch supports different types of search operations to accommodate various use cases.
5.4.1. Standard Search
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);
5.4.2. Multi-Search
Spring Data Meilisearch supports two types of multi-search operations: non-federated and federated multi-search.
Non-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
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.
5.4.3. Facet Search
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>
.
6. Meilisearch Repositories
This chapter describes how to use Meilisearch repositories.
6.1. Usage
Meilisearch repositories are used to store and retrieve data from Meilisearch.
public class MovieService {
private final MovieRepository repository;
public MovieService(MovieRepository repository) {
this.repository = repository;
}
public List<Movie> showAllMovies() {
return repository.findAll();
}
}
public interface MovieRepository extends MeilisearchRepository<Movie, String> {
}
6.2. Automatic creation of indexes with the corresponding mapping
If the @Document
annotation is present on the entity, the index will be created automatically with the corresponding mapping.
6.3. Lookup methods
The Meilisearch repository provides the following lookup methods:
-
findById(String id)
-
findAllById(Iterable<String> ids)
-
findAll()
-
findAll(Sort sort)
-
findAll(Pageable pageable)
Note that the above methods perform different behaviors.
The findById
and findAllById
methods use the Get one document or Get documents API.
However, the findAll
method uses the Search API.
This difference in behavior can also cause results to show differently.
For example, displayedAttributes in Meilisearch Settings does not work with the Get one document or Get documents API.
Go to Meilisearch documentation for more information.
|
6.4. Annotation based configuration
The Spring Data Meilisearch repositories can be configured using the @EnableMeilisearchRepositories
annotation.
@Configuration
@EnableMeilisearchRepositories( (1)
basePackages = {"com.example.repositories"}
)
public class Config {
@Bean
public MeilisearchOperations meilisearchTemplate() { (2)
// ...
}
}
1 | The @EnableMeilisearchRepositories annotation enables Meilisearch repositories.
If no basePackages are configured, the annotation will scan the package of the annotated configuration class. |
2 | Provide a MeilisearchOperations bean to override the default MeilisearchTemplate bean. |
6.5. Spring Namespace
The Spring Data Meilisearch repositories can be configured using the meilisearch
namespace.
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:meilisearch="http://www.vanslog.io/spring/data/meilisearch"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.vanslog.io/spring/data/meilisearch
http://www.vanslog.io/spring/data/meilisearch/spring-meilisearch-1.0.xsd">
<meilisearch:repositories base-package="com.example.repositories"/> (1)
<bean name="meilisearchTemplate" (2)
class="io.vanslog.spring.data.meilisearch.core.MeilisearchTemplate">
<constructor-arg name="meilisearchClient" ref="meilisearchClient"/> (3)
</bean>
<meilisearch:meilisearch-client id="meilisearchClient" api-key="masterKey"/> (4)
<bean id="jsonHandler" class="com.meilisearch.sdk.json.GsonJsonHandler"/> (5)
</beans>
1 | The meilisearch:repositories element enables Meilisearch repositories.
If no base-package is configured, the namespace will scan the package of the configuration file. |
2 | The meilisearchTemplate bean must be configured with a MeilisearchClient . |
3 | Set the client bean as the constructor argument of the meilisearchTemplate . |
4 | Configure the Meilisearch client with the api-key attribute. |
5 | Configure the jsonHandler bean with the GsonJsonHandler class. |
7. Meilisearch Settings
This chapter covers how to modify search settings in Meilisearch.
7.1. Overview
Meilisearch settings are used to define the behavior of the search engine.
Using @Setting
annotation, you can define the basic settings for the index, while complex settings are defined using separate annotations like @TypoTolerance
, @Faceting
, and @Pagination
.
@Document(indexUid = "products")
@Setting(
searchableAttributes = { "description", "brand", "color" },
displayedAttributes = { "description", "brand", "color", "productId" },
sortableAttributes = { "productId" },
rankingRules = { "typo", "words", "proximity", "attribute", "sort", "exactness" },
distinctAttribute = "productId",
filterableAttributes = { "brand", "color", "price" },
synonyms = {
@Synonym(word = "phone", synonyms = { "mobile", "cellphone" }),
@Synonym(word = "laptop", synonyms = { "notebook" })
},
dictionary = { "netflix", "spotify" },
stopWords = { "a", "an", "the" },
separatorTokens = { "-", "_", "@" },
nonSeparatorTokens = { ".", "#" },
proximityPrecision = "byWord",
searchCutoffMs = 50
)
@TypoTolerance(
enabled = true,
minWordSizeForTypos = @MinWordSizeForTypos(oneTypo = 5, twoTypos = 9),
disableOnWords = {"skype", "zoom"},
disableOnAttributes = {"serial_number"}
)
@Faceting(maxValuesPerFacet = 100)
@Pagination(maxTotalHits = 2000)
class Product {
@Id private String id;
private String description;
private String brand;
private String color;
private String productId;
private double price;
}
7.2. Search Settings
These settings control how documents are searched and ranked in Meilisearch.
7.2.1. Searchable and Displayed Attributes
@Document(indexUid = "products")
@Setting(
searchableAttributes = { "description", "brand", "color" }, (1)
displayedAttributes = { "description", "brand", "color", "productId" } (2)
)
class Product {
@Id private String id;
private String description;
private String brand;
private String color;
private String productId;
}
1 | The searchableAttributes attribute is used to define the fields that must be used for searching the results. |
2 | The displayedAttributes attribute is used to define the fields that must be displayed in the results. |
7.2.2. Ranking and Sorting
@Document(indexUid = "products")
@Setting(
sortableAttributes = { "productId" }, (1)
rankingRules = { "typo", "words", "proximity", "attribute", "sort", "exactness" }, (2)
distinctAttribute = "productId" (3)
)
class Product {
@Id private String id;
private String productId;
// other fields...
}
1 | The sortableAttributes attribute is used to define the fields that can be used for sorting the results. |
2 | The rankingRules attribute is used to define the ranking rules that must be used for sorting the results. |
3 | The distinctAttribute attribute is used to define the field that must be used to remove duplicates from the results. |
7.2.3. Filtering and Faceting
@Document(indexUid = "products")
@Setting(
filterableAttributes = { "brand", "color", "price" } (1)
)
@Faceting(maxValuesPerFacet = 100) (2)
class Product {
@Id private String id;
private String brand;
private String color;
private double price;
// other fields...
}
1 | The filterableAttributes attribute defines which fields can be used for filtering and faceted search. |
2 | The @Faceting annotation configures faceted search behavior. |
7.3. Text Processing Settings
These settings control how Meilisearch processes text during indexing and searching.
7.3.1. Synonyms and Dictionary
@Document(indexUid = "products")
@Setting(
synonyms = { (1)
@Synonym(
word = "phone",
synonyms = { "mobile", "cellphone" }
),
@Synonym(
word = "laptop",
synonyms = { "notebook" }
)
},
dictionary = { "netflix", "spotify" }, (2)
stopWords = { "a", "an", "the" } (3)
)
class Product {
@Id private String id;
// fields...
}
1 | The synonyms attribute defines groups of words that should be considered equivalent in search. |
2 | The dictionary attribute adds custom words to the internal dictionary. |
3 | The stopWords attribute is used to define the stop words that must be used for searching the results. |
7.3.2. Typo Tolerance
@Document(indexUid = "products")
@TypoTolerance( (1)
enabled = true,
minWordSizeForTypos = @MinWordSizeForTypos(
oneTypo = 5,
twoTypos = 9
),
disableOnWords = {"skype", "zoom"},
disableOnAttributes = {"serial_number"}
)
class Product {
@Id private String id;
// fields...
}
1 | The @TypoTolerance annotation configures the typo tolerance behavior of the search engine. |
7.3.3. Tokenization
@Document(indexUid = "products")
@Setting(
separatorTokens = { "-", "_", "@" }, (1)
nonSeparatorTokens = { ".", "#" } (2)
)
class Product {
@Id private String id;
// fields...
}
1 | The separatorTokens attribute defines which characters should be considered as word separators. |
2 | The nonSeparatorTokens attribute defines which characters should not be considered as word separators. |
7.3.4. Localization
By default, Meilisearch automatically detects the languages used in your documents.
However, you can explicitly define which languages are present in your dataset and in which fields using localized attributes configuration.
You can configure localized attributes using the @LocalizedAttribute
annotation within the @Setting
annotation.
Note that configuring multiple languages for a single index may impact search performance.
@Document(indexUid = "products")
@Setting(
localizedAttributes = {
@LocalizedAttribute(
attributePatterns = { "*En" }, (1)
locales = { "eng" } (2)
)
}
)
class Product {
@Id private String id;
private String nameEn;
// fields...
}
1 | The attributePatterns specify which fields contain content in the specified languages using patterns.
These patterns support wildcards (*) to match multiple fields with similar naming conventions:
|
2 | The locales attribute defines the list of supported languages for the specified attributes.
Each locale follows the ISO-639-3 three-letter, or alternatively use ISO-639-1 two-letter equivalents.
For a complete list of supported languages, refer to the Meilisearch documentation. |
Language Detection Control
Meilisearch provides two mechanisms for controlling language detection:
-
Query-level Language Control: If Meilisearch is detecting incorrect languages because of the query text, you can explicitly set the search language using the
locales
parameter in your search request. -
Document-level Language Control: If Meilisearch is detecting incorrect languages in your documents, use
localizedAttributes
to explicitly set the document languages at the index level.
For complete control over language detection during both indexing and search time, you can use both locales
and localizedAttributes
together.
7.4. Performance Settings
These settings control the performance aspects of Meilisearch.
@Document(indexUid = "products")
@Setting(
proximityPrecision = "byWord", (1)
searchCutoffMs = 50 (2)
)
class Product {
@Id private String id;
// fields...
}
1 | The proximityPrecision attribute defines how word proximity is calculated ("byWord", "byAttribute") |
2 | The searchCutoffMs attribute sets the maximum processing time for a search query in milliseconds. |
7.5. Pagination
Meilisearch’s search function is limited to return a maximum of 1,000 results.
Therefore, search(SearchRequest searchRequest, Class<?> clazz)
in MeilisearchOperation can’t return more than 1,000 results.
If you have more than 1,000 results, you must use @Pagination
annotation to extract the remaining results.
@Document(indexUid = "products")
@Pagination(maxTotalHits = 2000) (1)
class Product {
@Id private String id;
// fields...
}
1 | The maxTotalHits is used to define the maximum number of results that can be returned by the search engine. |