00:00

NavigableMap Interface in Java – Guide with Examples

NavigableMap is a SortedMap with methods for finding the nearest key to any value, taking key ranges with the bounds you choose, walking the map backwards, and removing the first or last entry.

It was added to java.util in Java 6 and is implemented by TreeMap and ConcurrentSkipListMap. These methods turn many "look up by range" problems, such as rates by date, floors by number or jobs by time, into one line of code.

1. What Is a NavigableMap?

A NavigableMap answers questions a plain map cannot: "What was the interest rate on 10 March?" (the latest change on or before that date), "Which floor is just below floor 6?" or "Give me every entry from 2 to 8, including both ends".

  • Everything from SortedMap: sorted keys, firstKey(), lastKey(), range views.
  • Nearest-key lookups in two flavours: returning the key (floorKey()) or the whole entry (floorEntry()).
  • Ranges with inclusive or exclusive bounds.
  • Reverse views: descendingMap() and descendingKeySet().
  • Polling: pollFirstEntry() and pollLastEntry() remove and return the ends.

2. Declaration and Hierarchy

java.util.NavigableMap
public interface NavigableMap<K, V> extends SortedMap<K, V>
Hierarchy
Map
 └─ SortedMap
     └─ NavigableMap
         ├─ TreeMap
         └─ ConcurrentNavigableMap
             └─ ConcurrentSkipListMap

3. Lookup by Date: floorEntry()

Store each change date as a key. floorEntry(date) then returns the change that was in effect on that date:

RateOnDate.java
import java.time.LocalDate;
import java.util.NavigableMap;
import java.util.TreeMap;

public class RateOnDate {
    public static void main(String[] args) {
        // Date a new interest rate starts -> rate in %
        NavigableMap<LocalDate, Double> rates = new TreeMap<>();
        rates.put(LocalDate.of(2024, 1, 1), 6.5);
        rates.put(LocalDate.of(2024, 7, 1), 6.25);
        rates.put(LocalDate.of(2025, 2, 15), 6.0);

        for (LocalDate d : new LocalDate[] {
                LocalDate.of(2024, 3, 10), LocalDate.of(2024, 7, 1), LocalDate.of(2025, 6, 30), LocalDate.of(2023, 12, 31)}) {
            var entry = rates.floorEntry(d);          // the latest change on or before d
            System.out.println(d + " -> " + (entry == null ? "no rate yet" : entry.getValue() + "% (since " + entry.getKey() + ")"));
        }
    }
}
Output
2024-03-10 -> 6.5% (since 2024-01-01)
2024-07-01 -> 6.25% (since 2024-07-01)
2025-06-30 -> 6.0% (since 2025-02-15)
2023-12-31 -> no rate yet

The last date is before the first rate, so floorEntry() returned null. Always handle that case.

4. All Navigation Methods in Action

NavigableMapMethods.java
import java.util.NavigableMap;
import java.util.TreeMap;

public class NavigableMapMethods {
    public static void main(String[] args) {
        NavigableMap<Integer, String> floors = new TreeMap<>();
        floors.put(0, "Lobby");
        floors.put(2, "Cafe");
        floors.put(5, "Offices");
        floors.put(8, "Gym");
        floors.put(10, "Roof");

        System.out.println("lowerKey(5):    " + floors.lowerKey(5));
        System.out.println("floorEntry(6):  " + floors.floorEntry(6));
        System.out.println("ceilingKey(6):  " + floors.ceilingKey(6));
        System.out.println("higherEntry(8): " + floors.higherEntry(8));

        System.out.println("headMap(5, true):           " + floors.headMap(5, true));
        System.out.println("tailMap(5, false):          " + floors.tailMap(5, false));
        System.out.println("subMap(2, true, 8, true):   " + floors.subMap(2, true, 8, true));
        System.out.println("descendingMap():            " + floors.descendingMap());
        System.out.println("descendingKeySet():         " + floors.descendingKeySet());
        System.out.println("navigableKeySet().floor(7): " + floors.navigableKeySet().floor(7));
    }
}
Output
lowerKey(5):    2
floorEntry(6):  5=Offices
ceilingKey(6):  8
higherEntry(8): 10=Roof
headMap(5, true):           {0=Lobby, 2=Cafe, 5=Offices}
tailMap(5, false):          {8=Gym, 10=Roof}
subMap(2, true, 8, true):   {2=Cafe, 5=Offices, 8=Gym}
descendingMap():            {10=Roof, 8=Gym, 5=Offices, 2=Cafe, 0=Lobby}
descendingKeySet():         [10, 8, 5, 2, 0]
navigableKeySet().floor(7): 5

5. NavigableMap Methods

MethodReturns
lowerKey(k), lowerEntry(k)Greatest key strictly less than k.
floorKey(k), floorEntry(k)Greatest key less than or equal to k.
ceilingKey(k), ceilingEntry(k)Smallest key greater than or equal to k.
higherKey(k), higherEntry(k)Smallest key strictly greater than k.
firstEntry(), lastEntry()Smallest and largest entries, or null if empty.
pollFirstEntry(), pollLastEntry()Remove and return the smallest or largest entry.
headMap(to, inclusive)View of keys below to.
tailMap(from, inclusive)View of keys above from.
subMap(from, fromInc, to, toInc)View of keys between two bounds.
descendingMap()Reverse-order view of the map.
navigableKeySet(), descendingKeySet()Keys as a NavigableSet, ascending or descending.

The …Entry methods return immutable snapshot entries: calling setValue() on them throws UnsupportedOperationException. Use put() to change a value.

6. Processing Entries in Order with pollFirstEntry()

pollFirstEntry() removes the smallest key and returns its entry, which makes a sorted map work like a simple time-ordered queue:

ScheduledJobs.java
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;

public class ScheduledJobs {
    public static void main(String[] args) {
        NavigableMap<Integer, String> jobs = new TreeMap<>();   // run time (minute of day) -> job
        jobs.put(540, "send reports");
        jobs.put(60, "backup database");
        jobs.put(720, "clear cache");
        jobs.put(300, "rotate logs");

        int now = 600;
        System.out.println("Jobs due by minute " + now + ":");
        Map.Entry<Integer, String> next;
        while ((next = jobs.firstEntry()) != null && next.getKey() <= now) {
            jobs.pollFirstEntry();                                // remove it as we run it
            System.out.println("  running " + next.getValue() + " (scheduled at " + next.getKey() + ")");
        }
        System.out.println("Still waiting: " + jobs);
    }
}
Output
Jobs due by minute 600:
  running backup database (scheduled at 60)
  running rotate logs (scheduled at 300)
  running send reports (scheduled at 540)
Still waiting: {720=clear cache}

Keys are unique, so two jobs at the same minute would overwrite each other. Use a NavigableMap<Integer, List<String>> or a PriorityQueue when duplicates are possible.

7. ConcurrentSkipListMap

For sorted data shared between threads, ConcurrentSkipListMap implements ConcurrentNavigableMap. It allows concurrent reads and writes without one global lock and offers all navigation methods.

ConcurrentSortedMap.java
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

public class ConcurrentSortedMap {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentNavigableMap<Integer, String> orders = new ConcurrentSkipListMap<>();

        Thread a = new Thread(() -> { for (int i = 1; i <= 500; i += 2) orders.put(i, "A"); });
        Thread b = new Thread(() -> { for (int i = 2; i <= 500; i += 2) orders.put(i, "B"); });
        a.start(); b.start();
        a.join();  b.join();

        System.out.println("size=" + orders.size() + ", firstKey=" + orders.firstKey() + ", lastKey=" + orders.lastKey());
        System.out.println("headMap(4): " + orders.headMap(4));
        System.out.println("ceilingKey(250): " + orders.ceilingKey(250));
    }
}
Output
size=500, firstKey=1, lastKey=500
headMap(4): {1=A, 2=B, 3=A}
ceilingKey(250): 250

Its iterators are weakly consistent (no ConcurrentModificationException), it does not allow null keys or values, and its size() is O(n).

8. NavigableMap vs SortedMap

FeatureSortedMapNavigableMap
IntroducedJDK 1.2Java 6
Nearest-key lookupsNoYes, keys and entries
Range boundsLower inclusive, upper exclusiveEach bound configurable
Reverse viewreversed() (Java 21)descendingMap()
Remove smallest/largestpollFirstEntry() (Java 21)pollFirstEntry(), pollLastEntry()

9. Common Use Cases

  • Values that change over time: prices, rates or configuration in effect on a date (floorEntry).
  • Bands and tiers: tax slabs, grades, shipping costs.
  • Nearest neighbour: closest available seat, slot or server.
  • Time-ordered processing: scheduled jobs, event timelines.
  • Leaderboards: top or bottom scores via descendingMap().

10. Summary

  • NavigableMap extends SortedMap with navigation methods.
  • lower, floor, ceiling and higher come in key and entry versions and return null when nothing matches.
  • Range views accept inclusive flags; descendingMap() gives a reverse view.
  • pollFirstEntry() and pollLastEntry() remove the ends.
  • TreeMap is the standard implementation; ConcurrentSkipListMap is the concurrent one.

Related Tutorials

Reference: Java SE 21 API: java.util.NavigableMap