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)

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