Listing Search Pipeline
End-to-end flow from request to response.
ListingRequest
↓
1. Parse filters (city, dates, guests, amenities, strategy)
↓
2. Filter listings from DB (dynamic SQL, joins only when needed)
↓
3. Concurrent fetch: availability + offers
↓
4. Process each listing in PARALLEL:
├─ Build property candidates (property + live availability)
├─ Cap guest requirements to listing's own capacity
├─ Invoke picking strategy → selected properties + quantities
├─ Calculate price (nightly rates + extra guest charges + tax)
├─ Resolve offers (promotion → bank offer → cancellation plans)
└─ Return ProcessedListing
↓
5. Filter: keep only listings with price > 0
↓
6. Split: "fits alone" vs "needs combining"
↓
7. Sort single-fit by score (best first)
↓
8. If allowCombination and few single results:
└─ Build multi-listing groups via MultiListingCombinationService
↓
9. Map to response (ListingSearchResponse)
Step-by-Step Detail
1. Parse Filters
ListingFilter.fromRawParams() extracts structured filters from the generic rawParams list:
- Location: city, state, country, locality
- Dates: checkInDate, checkOutDate →
StayDates(nights calculated viaChronoUnit.DAYS.between) - Guests: adults, children, infants, pets
- Property: propertyType, brand, amenities, tags, petFriendly, stressProperties
- Price: minPrice, maxPrice
- Search: searchQuery (text search on title/name)
- Strategy: strategy (GREEDY, OPTIMAL, CHEAPEST_FIRST, HIGHEST_RATED, MIN_WASTE)
- Combination: allowCombination, maxListingsInGroup, proximityMeters
2. Filter Listings (Dynamic SQL)
The ListingSearchRepository builds SQL dynamically — joins are only added when needed:
- Listing-only filters (city, state, locality, searchQuery on title): no property join
- Property-level filters (brand, type, petFriendly, price, guests): join
listing_property+properties - Aggregate filters (minBedrooms, minAdults): added as
HAVINGclauses
3. Concurrent Data Fetch
Two operations run simultaneously via CompletableFuture:
| Operation | Source | Returns |
|---|---|---|
| Availability | ChannelRateDao → PostgreSQL | Map of propertyId → nightly rates + inventory |
| Offers | OfferCacheService → in-memory cache | Map of listingId → promotions, bank offers, cancellation plans |
4. Process Each Listing
Parallel via listings.parallelStream(). For each listing:
- Build
PropertyCandidateobjects (Property + live availability) - If dates present, availability data is merged; unavailable properties get filtered
- Cap requirements to listing's own capacity (for combinations: partial fulfillment is OK)
- Invoke the picking strategy → returns selected properties with quantities
- Calculate total price and average rating
5-7. Filter, Split, Sort
- Listings with
selectedPrice == 0are discarded - "Fits alone" = listing's total capacity covers the full guest requirement
- Sorted by
listingScore(lower is better): considers price + rating
8. Combinations (Optional)
Only triggered when allowCombination=true and single results are fewer than minSingleResults (default 3).
See Combinations for details.