Skip to content

Sorting

Sorting controls the order in which search results are returned. You can sort by relevance score, field values, or a combination.

Basic Usage

// Sort by score (default)
QueryBuilder.matchQuery("title", "java")
    .buildForageQuery(10)

// Sort by field
QueryBuilder.matchQuery("title", "java")
    .buildForageQuery(10, Arrays.asList(
        new SortCriteria("rating", SortOrder.DESC)
    ))

Sort Criteria

By Score

// Highest score first (default for most searches)
SortCriteria.byScore(SortOrder.DESC)

// Lowest score first (unusual, but available)
SortCriteria.byScore(SortOrder.ASC)

By Field

// By rating, highest first
new SortCriteria("rating", SortOrder.DESC)

// By year, oldest first
new SortCriteria("year", SortOrder.ASC)

// By title, alphabetical
new SortCriteria("title", SortOrder.ASC)

Sort Order

Order Description Example
DESC Descending (high to low) 5, 4, 3, 2, 1
ASC Ascending (low to high) 1, 2, 3, 4, 5

Multiple Sort Criteria

Results are sorted by the first criterion, then by subsequent criteria as tiebreakers:

QueryBuilder.matchQuery("title", "programming")
    .buildForageQuery(20, Arrays.asList(
        SortCriteria.byScore(SortOrder.DESC),       // Primary: relevance
        new SortCriteria("rating", SortOrder.DESC), // Tiebreaker: rating
        new SortCriteria("year", SortOrder.DESC)    // Second tiebreaker: year
    ))

Examples

Relevance Only

// Default behavior - sort by relevance score
QueryBuilder.matchQuery("title", "java")
    .buildForageQuery(10)

// Explicit relevance sort
QueryBuilder.matchQuery("title", "java")
    .buildForageQuery(10, Arrays.asList(
        SortCriteria.byScore(SortOrder.DESC)
    ))

By Rating

// Top rated books matching "programming"
QueryBuilder.matchQuery("title", "programming")
    .buildForageQuery(10, Arrays.asList(
        new SortCriteria("rating", SortOrder.DESC)
    ))

By Date

// Most recent first
QueryBuilder.matchQuery("category", "news")
    .buildForageQuery(20, Arrays.asList(
        new SortCriteria("publishedAt", SortOrder.DESC)
    ))

// Oldest first
QueryBuilder.matchQuery("category", "archive")
    .buildForageQuery(20, Arrays.asList(
        new SortCriteria("publishedAt", SortOrder.ASC)
    ))

Relevance + Tiebreaker

// Sort by relevance, use rating to break ties
QueryBuilder.matchQuery("title", "java")
    .buildForageQuery(20, Arrays.asList(
        SortCriteria.byScore(SortOrder.DESC),
        new SortCriteria("rating", SortOrder.DESC)
    ))

Price Sorting

// Low to high price
QueryBuilder.matchQuery("category", "laptops")
    .buildForageQuery(50, Arrays.asList(
        new SortCriteria("price", SortOrder.ASC)
    ))

// High to low price
QueryBuilder.matchQuery("category", "laptops")
    .buildForageQuery(50, Arrays.asList(
        new SortCriteria("price", SortOrder.DESC)
    ))

With Function Score

When using function score, sort by score to see the computed scores:

// Function score results sorted by computed score
QueryBuilder.functionScoreQuery()
    .baseQuery(QueryBuilder.matchQuery("title", "java").build())
    .fieldValueFactor("rating")
    .buildForageQuery(10, Arrays.asList(
        SortCriteria.byScore(SortOrder.DESC)
    ))

With Filters

// Filter then sort by field
QueryBuilder.booleanQuery()
    .query(QueryBuilder.matchQuery("category", "technology").build())
    .query(QueryBuilder.floatRangeQuery("rating", 4.0f, 5.0f).build())
    .clauseType(ClauseType.MUST)
    .buildForageQuery(20, Arrays.asList(
        new SortCriteria("rating", SortOrder.DESC),
        new SortCriteria("year", SortOrder.DESC)
    ))

Field Types and Sorting

Field Type Sortable Notes
IntField Yes Numeric sort
FloatField Yes Numeric sort
StringField Yes Lexicographic sort
TextField No Use StringField for sortable text

TextField Sorting

TextField is analyzed and tokenized, making it unsuitable for sorting. If you need to sort by a text field, index it as both:

// Searchable + Sortable
new TextField("title", book.getTitle()),      // For searching
new StringField("title_sort", book.getTitle()) // For sorting

Common Patterns

E-Commerce Sorting Options

public ForageQuery buildProductQuery(String searchTerm, String sortBy) {
    List<SortCriteria> sort = switch (sortBy) {
        case "relevance" -> Arrays.asList(SortCriteria.byScore(SortOrder.DESC));
        case "price_low" -> Arrays.asList(new SortCriteria("price", SortOrder.ASC));
        case "price_high" -> Arrays.asList(new SortCriteria("price", SortOrder.DESC));
        case "rating" -> Arrays.asList(new SortCriteria("rating", SortOrder.DESC));
        case "newest" -> Arrays.asList(new SortCriteria("createdAt", SortOrder.DESC));
        default -> Arrays.asList(SortCriteria.byScore(SortOrder.DESC));
    };

    return QueryBuilder.matchQuery("name", searchTerm)
        .buildForageQuery(20, sort);
}

Blog Post Listing

// Most recent posts first
QueryBuilder.matchQuery("status", "published")
    .buildForageQuery(20, Arrays.asList(
        new SortCriteria("publishedAt", SortOrder.DESC)
    ))

Leaderboard

// Top scores
QueryBuilder.matchAllQuery()
    .buildForageQuery(100, Arrays.asList(
        new SortCriteria("score", SortOrder.DESC),
        new SortCriteria("timestamp", SortOrder.ASC)  // Earlier wins ties
    ))

Performance Considerations

  • Field-based sorting is fast when fields have DocValues
  • All numeric fields (IntField, FloatField) automatically have DocValues
  • Sorting doesn't significantly impact query performance