Vector Search with Infinispan

What You Will Learn

How to store entities with vector embeddings in Infinispan and perform kNN (k-nearest neighbor) vector searches, including hybrid queries that combine vector similarity with full-text search and metadata filters.

Prerequisites

  • Java 17+

  • An Infinispan Server running on localhost:11222 (or Docker/Podman available for Testcontainers)

Start an Infinispan Server with Docker or Podman:

docker run -it --rm -p 11222:11222 -e USER=admin -e PASS=password quay.io/infinispan/server:latest
Tip
You can replace docker with podman in the command above if you use Podman.
Tip
If no server is running, the tutorial code automatically starts an Infinispan Server using Testcontainers.

Step 1: Define an Indexed Entity with a Vector Field

Use the @Vector annotation to declare a vector field with its dimension and similarity function:

@Proto
@Indexed
public record Beer(
      @Keyword(projectable = true, sortable = true)
      String name,

      @Keyword(projectable = true, normalizer = "lowercase")
      String style,

      @Keyword(projectable = true, sortable = true, normalizer = "lowercase")
      String brewery,

      @Keyword(projectable = true, normalizer = "lowercase")
      String country,

      @Basic(projectable = true, sortable = true)
      Double abv,

      @Text
      String description,

      @Vector(dimension = 3, similarity = VectorSimilarity.COSINE)
      float[] descriptionEmbedding
) {
}

Step 2: Connect and Register the Schema

Connect to the server and register the Protobuf schema for your entity:

   static void connect() throws Exception {
      ConfigurationBuilder builder = TutorialsConnectorHelper.connectionConfig();
      builder.addContextInitializer(new BeerSchemaImpl());

      URI cacheConfigURI = VectorSearchQuickstart.class.getClassLoader()
            .getResource("indexedCache.xml").toURI();
      builder.remoteCache(CACHE_NAME).configurationURI(cacheConfigURI);

      cacheManager = TutorialsConnectorHelper.connect(builder);
      cacheManager.administration().schemas().createOrUpdate(new BeerSchemaImpl());
      cache = cacheManager.getCache(CACHE_NAME);
   }

Use the <→ operator with ~:k to find the k nearest neighbors:

   static void knnVectorSearch() {
      System.out.println("=== kNN: 3 beers closest to 'dark roasty' vector [0.9, 0.1, 0.1] ===");
      Query<Beer> query = cache.query(
            "from quickstart.Beer b where b.descriptionEmbedding <-> [:v]~:k");
      query.setParameter("v", new float[]{0.9f, 0.1f, 0.1f});
      query.setParameter("k", 3);
      for (Beer beer : query.list()) {
         System.out.printf("  %-30s %s%n", beer.name(), beer.style());
      }
      System.out.println();
   }

Step 4: Run Hybrid Queries

Combine vector similarity with metadata filters using the filtering clause, or with full-text filters:

   static void hybridFilterByStyle() {
      System.out.println("=== Hybrid: closest to 'refreshing summer beer' [0.05, 0.95, 0.05], only lagers under 5% ABV ===");
      Query<Object[]> query = cache.query(
            "select score(b), b.name, b.style, b.abv from quickstart.Beer b " +
            "where b.descriptionEmbedding <-> [:v]~:k " +
            "filtering (b.style = 'Lager' and b.abv < 5.0)");
      query.setParameter("v", new float[]{0.05f, 0.95f, 0.05f});
      query.setParameter("k", 3);
      List<Object[]> results = query.list();
      if (results.isEmpty()) {
         System.out.println("  (no matches)");
      }
      for (Object[] row : results) {
         System.out.printf("  score=%.4f  %-30s %-10s %.1f%%%n", row[0], row[1], row[2], row[3]);
      }
      System.out.println();
   }

   static void hybridFilterByCountry() {
      System.out.println("=== Hybrid: closest to 'toasted malty caramel' [0.7, 0.3, 0.1], only Spanish beers ===");
      Query<Object[]> query = cache.query(
            "select score(b), b.name, b.style, b.abv from quickstart.Beer b " +
            "where b.descriptionEmbedding <-> [:v]~:k filtering b.country = 'Spain'");
      query.setParameter("v", new float[]{0.7f, 0.3f, 0.1f});
      query.setParameter("k", 3);
      for (Object[] row : query.list()) {
         System.out.printf("  score=%.4f  %-30s %-15s %.1f%%%n", row[0], row[1], row[2], row[3]);
      }
      System.out.println();
   }

   static void hybridFullTextAndVector() {
      System.out.println("=== Hybrid: closest to 'hoppy craft' [0.1, 0.1, 0.95], description mentions 'citrus' ===");
      Query<Object[]> query = cache.query(
            "select score(b), b.name, b.brewery, b.abv from quickstart.Beer b " +
            "where b.descriptionEmbedding <-> [:v]~:k " +
            "filtering b.description : 'citrus'");
      query.setParameter("v", new float[]{0.1f, 0.1f, 0.95f});
      query.setParameter("k", 5);
      for (Object[] row : query.list()) {
         System.out.printf("  score=%.4f  %-30s %-20s %.1f%%%n", row[0], row[1], row[2], row[3]);
      }
      System.out.println();
   }

Step 5: Run the Tutorial

mvn package exec:exec

The output demonstrates full-text search, keyword/range filters, projections, kNN vector search with score projections, and hybrid queries filtering by style, country, and description text.

What’s Next