Dates, text and numbers

java.time types, formatting and parsing, BigDecimal for money, and text blocks for readable strings.

java.time

Instant now = Instant.now();                                   // UTC timeline point
LocalDate today = LocalDate.now(ZoneId.of("Europe/London"));
LocalDateTime meeting = LocalDateTime.of(2026, 9, 18, 9, 30);

Duration elapsed = Duration.between(meeting, meeting.plusHours(2));   // PT2H
Period span = Period.between(LocalDate.of(2026, 1, 1), today);        // P8M17D

ZonedDateTime tokyo = meeting.atZone(ZoneId.of("Asia/Tokyo"));
Instant asInstant = tokyo.toInstant();
TypeRepresentsUse it for
InstantA point on the timeline, in UTCTimestamps, logs, storage
LocalDateA date with no time or zoneDeadlines and birthdays
LocalTimeA time of dayOpening hours
LocalDateTimeDate and time with no zoneLocal scheduling input
ZonedDateTimeDate and time in a zone, with rulesDisplaying to a user
DurationA time-based amountElapsed time, timeouts
PeriodA calendar-based amount"Three months later"
  • Store Instant in UTC and convert to a zone only for display or for calendar arithmetic.
  • Every java.time type is immutable, so plusDays returns a new object rather than mutating the receiver.
  • Duration and Period look similar and are not interchangeable: one counts seconds, the other counts calendar units and can produce different results across a daylight-saving change.
  • LocalDateTime cannot represent an ambiguous or skipped wall-clock time, which is exactly what happens at a DST transition — use ZonedDateTime there.
  • The older Date, Calendar and SimpleDateFormat classes are mutable and not thread-safe; do not use them in new code.

Formatting and parsing

DateTimeFormatter friendly =
    DateTimeFormatter.ofPattern("d MMM uuuu HH:mm", Locale.UK);

String text = meeting.format(friendly);              // "18 Sep 2026 09:30"
LocalDateTime parsed = LocalDateTime.parse(text, friendly);

String machine = DateTimeFormatter.ISO_INSTANT.format(Instant.now());
  • DateTimeFormatter is immutable and thread-safe, so build it once as a constant and reuse it — unlike the legacy formatter.
  • Use uuuu for the year in a pattern. yyyy means year-of-era and behaves oddly around dates before year 1; only use it when you specifically want the era-based year.
  • Parsing throws DateTimeParseException, so validate the string at the input boundary rather than deep inside the domain model.
  • Use Locale.ROOT for machine-facing output and the user's locale for anything a person reads.
  • ISO-8601 is the right wire format for storage and APIs; anything else invites a parsing argument.

Money, numbers and text blocks

BigDecimal price = new BigDecimal("19.99");       // the String constructor
BigDecimal total = price.multiply(BigDecimal.valueOf(3))
                        .setScale(2, RoundingMode.HALF_UP);

NumberFormat money = NumberFormat.getCurrencyInstance(Locale.UK);
String shown = money.format(total);

double surprise = 0.1 + 0.2;                      // 0.30000000000000004

String query = """
    SELECT id, sku
    FROM orders
    WHERE status = ?
    ORDER BY created_at DESC
    """;
  • Never construct a BigDecimal from a double: new BigDecimal(0.1) carries the binary rounding error into the value. Use the String constructor or BigDecimal.valueOf.
  • Always set a scale and a rounding mode for money, and decide the mode deliberately — half-up is common for display, not always correct for accounting.
  • divide without a rounding mode throws ArithmeticException for a non-terminating decimal such as one third.
  • A text block strips incidental indentation and keeps the line breaks you typed; add a trailing \ when you need to suppress the final newline.
  • Use NumberFormat or String.format for presentation instead of hard-coding separators and currency symbols.
⚠️
Binary floating point cannot represent most decimal fractions exactly, so double is the wrong type for money, quantities that must sum correctly, or anything that is reconciled. Use BigDecimal at the boundary and keep double for science and graphics.

FAQ

Should timestamps be stored as strings or instants?
As instants, either a native timestamp column or an ISO-8601 UTC string. Storing local wall-clock time without a zone makes daylight-saving bugs and multi-region bugs inevitable.
When is double acceptable?
For measurement, graphics, physics and statistics, where the input is approximate anyway. Not for money, not for counters that must reconcile, and not for values you compare for equality.

Generics, lambdas and the streams API Files, I/O and JSON

Last refreshed 2026-09-18.