Skip to content

Getting Started

This guide takes you from zero to a running index and search in about 5 minutes.


Prerequisites

  • Java 17 or higher
  • Maven 3.9+ (or equivalent Gradle)

1. Add the dependency

<dependency>
  <groupId>com.phonepe</groupId>
  <artifactId>mustang-core</artifactId>
  <version>${mustang.version}</version>
</dependency>
<dependency>
  <groupId>com.phonepe</groupId>
  <artifactId>mustang-dw-bundle</artifactId>
  <version>${mustang.version}</version>
</dependency>

The DW bundle transitively includes mustang-core.

Note: Find the latest version on Maven Central.


2. Create the engine

import com.fasterxml.jackson.databind.ObjectMapper;
import com.phonepe.mustang.MustangEngine;

ObjectMapper mapper = new ObjectMapper();

MustangEngine engine = MustangEngine.builder()
    .mapper(mapper)
    .build();

MustangEngine is thread-safe. Create a single instance per application and reuse it — it manages all internal index state.


3. Define a criteria

A criteria is a Boolean expression in DNF or CNF form. It has a unique string ID and is composed of predicates.

DNF — disjunction of conjunctions

Satisfied if any conjunction (AND-group) matches.

import com.phonepe.mustang.criteria.impl.DNFCriteria;
import com.phonepe.mustang.composition.impl.Conjunction;
import com.phonepe.mustang.predicate.impl.IncludedPredicate;
import com.phonepe.mustang.predicate.impl.ExcludedPredicate;
import com.phonepe.mustang.detail.impl.EqualityDetail;
import com.phonepe.mustang.detail.impl.RangeDetail;
import com.google.common.collect.Sets;

// Rule: (country ∈ {IN, SG} AND age ∈ [18,35])
//     OR (country ∈ {US} AND platform ∉ {ios})
Criteria rule = DNFCriteria.builder()
    .id("segment-001")
    .conjunction(Conjunction.builder()
        .predicate(IncludedPredicate.builder()
            .lhs("$.country")
            .detail(EqualityDetail.builder()
                .values(Sets.newHashSet("IN", "SG"))
                .build())
            .build())
        .predicate(IncludedPredicate.builder()
            .lhs("$.age")
            .detail(RangeDetail.builder()
                .lowerBound(18).includeLowerBound(true)
                .upperBound(35).includeUpperBound(true)
                .build())
            .build())
        .build())
    .conjunction(Conjunction.builder()
        .predicate(IncludedPredicate.builder()
            .lhs("$.country")
            .detail(EqualityDetail.builder()
                .values(Sets.newHashSet("US"))
                .build())
            .build())
        .predicate(ExcludedPredicate.builder()
            .lhs("$.platform")
            .detail(EqualityDetail.builder()
                .values(Sets.newHashSet("ios"))
                .build())
            .build())
        .build())
    .build();

CNF — conjunction of disjunctions

Satisfied if all disjunctions (OR-groups) match.

import com.phonepe.mustang.criteria.impl.CNFCriteria;
import com.phonepe.mustang.composition.impl.Disjunction;

// Rule: (tier ∈ {premium} OR credits > 100) AND (region ∈ {APAC, EMEA})
Criteria plan = CNFCriteria.builder()
    .id("plan-gate-001")
    .disjunction(Disjunction.builder()
        .predicate(IncludedPredicate.builder()
            .lhs("$.tier")
            .detail(EqualityDetail.builder()
                .values(Sets.newHashSet("premium"))
                .build())
            .build())
        .predicate(IncludedPredicate.builder()
            .lhs("$.credits")
            .detail(RangeDetail.builder()
                .lowerBound(100).includeLowerBound(false)
                .build())
            .build())
        .build())
    .disjunction(Disjunction.builder()
        .predicate(IncludedPredicate.builder()
            .lhs("$.region")
            .detail(EqualityDetail.builder()
                .values(Sets.newHashSet("APAC", "EMEA"))
                .build())
            .build())
        .build())
    .build();

4. Index the criteria

// Single
engine.add("my-index", rule);

// Bulk (more efficient for large initial loads)
engine.add("my-index", List.of(rule, plan, otherCriteria));

An index is created automatically the first time you add to it. The index name is an arbitrary string — use it to namespace different rule sets.


Build a RequestContext from your event JSON and call search:

import com.fasterxml.jackson.databind.JsonNode;
import com.phonepe.mustang.common.RequestContext;

// Event: user from India, age 25, on Android
JsonNode event = mapper.readTree("""
    {"country": "IN", "age": 25, "platform": "android"}
    """);

RequestContext context = RequestContext.builder()
    .node(event)
    .build();

// Returns IDs of all matching criteria
Set<String> matches = engine.search("my-index", context);
// → ["segment-001"]

lhs paths follow JSONPath syntax ($.field, $.nested.field, $.array[0], etc.).


6. Evaluate a single criteria

If you just need to evaluate one criteria without indexing:

boolean result = engine.evaluate(rule, context);
// → true

7. Debug

Get a per-predicate breakdown of why a criteria matched or didn't:

DebugResult debug = engine.debug(rule, context);
// debug.isResult() → true/false
// debug.getCompositionDebugResults() → per-conjunction/disjunction detail

What next?

Topic Link
All predicate types (Range, Regex, Versioning, Subset…) Predicates & Details
Value transformations before matching PreOperations
Scoring and top-N ranked search Scoring & Ranking
All engine API methods Indexing · Searching · Advanced
Worked end-to-end examples Examples
REST API via Dropwizard Dropwizard Bundle
How the algorithm works The Algorithm