TemporalQuery is a functional interface in java.time.temporal that reads information from a date or time object and returns it as any type, such as the zone of a ZonedDateTime or the fiscal quarter of a LocalDate. We run a query with the query() method, which every java.time class has through the TemporalAccessor interface.
Queries let us write one piece of date logic that works for LocalDate, LocalDateTime, ZonedDateTime and OffsetDateTime alike. The JDK uses them as well, for example when DateTimeFormatter.parse(text, LocalDate::from) turns parsed text into a LocalDate.
The following example runs three built-in queries, a method reference and a lambda on one order timestamp.
ZonedDateTime order = ZonedDateTime.of(2026, 3, 14, 9, 30, 0, 0, ZoneId.of("Europe/Paris"));
LocalDate date = order.query(TemporalQueries.localDate()); // 2026-03-14
ZoneId zone = order.query(TemporalQueries.zone()); // Europe/Paris
TemporalUnit precision = order.query(TemporalQueries.precision()); // Nanos
YearMonth month = order.query(YearMonth::from); // 2026-03
TemporalQuery<Boolean> isWeekend = t -> DayOfWeek.from(t).getValue() >= 6;
boolean weekend = order.query(isWeekend); // true
Notice that the query decides the return type, so the same query() call returns a LocalDate, a ZoneId or a Boolean. We cover the interface first, followed by the built-in queries in TemporalQueries, method references, custom queries for business hours and fiscal quarters, and a sales report that groups orders by quarter.
1. The TemporalQuery Interface
The TemporalQuery interface has one abstract method, queryFrom(TemporalAccessor), so a lambda expression or a method reference can implement it. The full signature is R queryFrom(TemporalAccessor temporal), where the type parameter R is the result type.
A TemporalAccessor is any object that can answer questions about date and time fields, and every java.time value class implements it, from LocalDate to Instant and YearMonth. Its method query(query) calls query.queryFrom(this), and the query() form is the recommended one because it reads better.

LocalDate day = LocalDate.of(2026, 3, 14);
YearMonth viaQuery = day.query(YearMonth::from); // 2026-03
YearMonth viaQueryFrom = ((TemporalQuery<YearMonth>) YearMonth::from).queryFrom(day); // 2026-03
A query only reads. It never changes the input, which is the job of a TemporalAdjuster.
2. Built-in Queries in TemporalQueries
The TemporalQueries class has seven static methods that return ready-made queries. Each one returns null when the object does not hold the requested information, so a LocalDateTime has no zone and an Instant has no local date.
| Query | Returns | LocalDate | LocalDateTime | ZonedDateTime | OffsetDateTime | Instant |
|---|---|---|---|---|---|---|
| localDate() | LocalDate | 2026-03-14 | 2026-03-14 | 2026-03-14 | 2026-03-14 | null |
| localTime() | LocalTime | null | 09:30 | 09:30 | 09:30 | null |
| zoneId() | ZoneId, strict | null | null | Europe/Paris | null | null |
| zone() | ZoneId, lenient | null | null | Europe/Paris | +02:00 | null |
| offset() | ZoneOffset | null | null | +01:00 | +02:00 | null |
| precision() | TemporalUnit | Days | Nanos | Nanos | Nanos | Nanos |
| chronology() | Chronology | ISO | ISO | ISO | ISO | null |
The values come from the same moment, March 14, 2026 at 09:30, created once per type. The difference between zoneId() and zone() shows in the OffsetDateTime column, where the strict query returns null and the lenient one falls back to the offset.
LocalDateTime local = LocalDateTime.of(2026, 3, 14, 9, 30);
OffsetDateTime offsetTime = OffsetDateTime.of(local, ZoneOffset.ofHours(2));
ZoneId strict = offsetTime.query(TemporalQueries.zoneId()); // null
ZoneId lenient = offsetTime.query(TemporalQueries.zone()); // +02:00
ZoneOffset offset = offsetTime.query(TemporalQueries.offset()); // +02:00
ZoneId noZone = local.query(TemporalQueries.zone()); // null
TemporalUnit dateOnly = LocalDate.of(2026, 3, 14).query(TemporalQueries.precision()); // Days
The query precision() returns the smallest unit the object holds. It answers “does this value have a time part?” for code that gets a TemporalAccessor of unknown type, for example a generic formatter or a validation library.
3. Method References as Queries
Most java.time value classes have a static from(TemporalAccessor) method, and that signature matches queryFrom(). So LocalDate::from, YearMonth::from, DayOfWeek::from and MonthDay::from all work as queries and extract one part of a richer value.
ZonedDateTime paid = ZonedDateTime.of(2026, 3, 14, 9, 30, 0, 0, ZoneId.of("Europe/Paris"));
LocalDate paidOn = paid.query(LocalDate::from); // 2026-03-14
YearMonth billingMonth = paid.query(YearMonth::from); // 2026-03
DayOfWeek weekday = paid.query(DayOfWeek::from); // SATURDAY
MonthDay yearlyDate = paid.query(MonthDay::from); // --03-14
LocalDate fromTime = LocalTime.of(9, 30).query(LocalDate::from); // DateTimeException
Unlike the built-in queries, the from() methods throw a DateTimeException when the information is missing, as the last line shows for a LocalTime. An instance method such as LocalDate::getMonth does not fit the interface, because it needs a LocalDate and the query receives a TemporalAccessor.
// does not compile: invalid method reference, getMonth() cannot be applied to TemporalAccessor
Month month = LocalDate.of(2026, 3, 14).query(LocalDate::getMonth);
The same method references drive parsing. The method DateTimeFormatter.parse(text, query) parses the text and runs the query on the result, and parseBest() tries several queries in order, which handles input with an optional time part.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd[ HH:mm]");
LocalDateTime withTime = formatter.parse("2026-03-14 10:15", LocalDateTime::from); // 2026-03-14T10:15
TemporalAccessor best = formatter.parseBest("2026-03-14", LocalDateTime::from, LocalDate::from); // 2026-03-14
In the last line, the text has no time, so LocalDateTime::from fails and parseBest() returns the LocalDate from the second query.
4. Writing Custom Queries
A custom query puts a business rule about dates in one place. Because it receives a TemporalAccessor, one query serves every type that has the fields it reads, and the caller does not need an overload per class.
4.1. Is a Time Within Business Hours?
A support desk answers chats from 09:00 to 17:00. The query reads the time of day with LocalTime.from(), so it accepts a LocalTime, a LocalDateTime or a ZonedDateTime. The opening time is included and the closing time is excluded, so 17:00 is already closed.
static boolean isBusinessHours(TemporalAccessor temporal) {
LocalTime time = LocalTime.from(temporal);
return !time.isBefore(LocalTime.of(9, 0)) && time.isBefore(LocalTime.of(17, 0));
}
TemporalQuery<Boolean> businessHours = t -> isBusinessHours(t); // a query built from the method
boolean morning = LocalTime.of(9, 0).query(businessHours); // true
boolean closing = LocalDateTime.of(2026, 3, 13, 17, 0).query(businessHours); // false
boolean zoned = ZonedDateTime.of(2026, 3, 13, 16, 59, 0, 0, ZoneId.of("Asia/Kolkata")).query(businessHours); // true
For a ZonedDateTime, the query checks the local time in that value’s zone. When the desk works in one fixed zone, we convert the timestamp first with withZoneSameInstant(deskZone), so a chat from another country is checked against the desk’s clock.
4.2. Fiscal Quarter of a Date
For the calendar quarter, Java has a field, so date.get(IsoFields.QUARTER_OF_YEAR) returns 1 to 4. Many companies use a fiscal year that starts in another month, such as April in India and Japan, and a query with the first month as a parameter covers every such calendar.
record FiscalQuarter(Month firstMonth) implements TemporalQuery<Integer> {
@Override
public Integer queryFrom(TemporalAccessor temporal) {
int month = temporal.get(ChronoField.MONTH_OF_YEAR);
int monthsIntoYear = Math.floorMod(month - firstMonth.getValue(), 12);
return monthsIntoYear / 3 + 1;
}
}
TemporalQuery<Integer> aprilYear = new FiscalQuarter(Month.APRIL);
int calendarQuarter = LocalDate.of(2026, 3, 14).get(IsoFields.QUARTER_OF_YEAR); // 1
int march = LocalDate.of(2026, 3, 14).query(aprilYear); // 4
int april = LocalDate.of(2026, 4, 1).query(aprilYear); // 1
int december = YearMonth.of(2026, 12).query(aprilYear); // 3
int noDate = LocalTime.NOON.query(aprilYear); // UnsupportedTemporalTypeException
Integer division needs the month minus one, or an offset as in the record, because month / 3 + 1 puts March in quarter 2 and December in quarter 5. The query also works on a YearMonth, which has a month but no day, and throws an UnsupportedTemporalTypeException for a LocalTime.
5. Counting Orders per Fiscal Quarter for a Dashboard
An online store’s dashboard shows the number of orders per fiscal quarter. The orders come from a REST API as OffsetDateTime values in the customer’s offset, and the company counts quarters in its own zone, Asia/Kolkata. An order placed late on March 31 in New York is already April 1 in India, so it belongs to the next fiscal year’s first quarter.
static Map<Integer, Long> ordersPerQuarter(List<OffsetDateTime> orders, ZoneId companyZone) {
TemporalQuery<Integer> quarter = new FiscalQuarter(Month.APRIL);
return orders.stream()
.map(o -> o.atZoneSameInstant(companyZone))
.collect(Collectors.groupingBy(z -> z.query(quarter), TreeMap::new, Collectors.counting()));
}
List<OffsetDateTime> orders = List.of(
OffsetDateTime.parse("2026-03-31T21:00-04:00"),
OffsetDateTime.parse("2026-03-02T10:00+01:00"),
OffsetDateTime.parse("2026-05-20T08:00+05:30"));
Map<Integer, Long> report = ordersPerQuarter(orders, ZoneId.of("Asia/Kolkata")); // {1=2, 4=1}
The first order is 06:30 on April 1 in Kolkata, so quarter 1 has two orders. Without the zone conversion, the query would read March 31 from the OffsetDateTime and count the order in quarter 4. The steps to get the start and end dates of a quarter are in current quarter start and end dates.
6. TemporalQuery vs Getters and TemporalAdjuster
A query is not always the best choice. When the code works with one known type, a getter such as date.getMonth() is shorter and clearer. Queries pay off when one rule must accept several types, or when an API such as DateTimeFormatter asks for one.
| Need | Use | Example |
|---|---|---|
| Read a field of a known type | Getter | date.getDayOfWeek() |
| Read one field of any type | get(field) | t.get(ChronoField.MONTH_OF_YEAR) |
| Extract a type or zone, or null | Built-in query | t.query(TemporalQueries.zone()) |
| Apply a business rule to any type | Custom TemporalQuery | t.query(new FiscalQuarter(APRIL)) |
| Move to another date | TemporalAdjuster | date.with(lastDayOfMonth()) |
7. TemporalQuery FAQs
TemporalQuery code in real projects raises four recurring questions, about null results, zones, quarters and the precision query.
7.1. Why does query() return null?
The built-in queries in TemporalQueries return null when the object does not contain the requested part, such as localTime() on a LocalDate. Method references such as LocalDate::from throw a DateTimeException instead, and a custom query decides for itself, so we document the behavior of our own queries.
7.2. What is the difference between TemporalQueries.zone() and zoneId()?
The query zoneId() returns only a region zone such as Europe/Paris, so it returns null for an OffsetDateTime. The query zone() returns the region zone when there is one and the offset otherwise.
7.3. How do we get the quarter of a date in Java?
For the calendar quarter, we call date.get(IsoFields.QUARTER_OF_YEAR), which returns 1 to 4. When the fiscal year begins in a month other than January, we use a query such as FiscalQuarter in section 4.
7.4. What does TemporalQueries.precision() return?
The smallest unit the object supports, as a ChronoUnit. A LocalDate returns DAYS, a YearMonth returns MONTHS, and LocalDateTime, ZonedDateTime and Instant return NANOS.
8. Conclusion
A TemporalQuery reads information from any java.time object and returns it in the type the query chooses. The TemporalQueries class covers dates, times, zones, offsets, precision and chronology and returns null for missing parts, whereas from() method references throw an exception.
Custom queries hold business rules such as business hours or fiscal quarters in one place and accept every type that has the needed fields. For one known type, getters remain the simpler choice. Related topics are listed in our Java date and time guides.
9. References
- TemporalQuery (Java SE 25 API)
- TemporalQueries (Java SE 25 API)
- TemporalAccessor (Java SE 25 API)
- IsoFields (Java SE 25 API)
Happy Learning !!