The LocalDateTime class in Java represents a date and a time of day together, such as 2026-05-14T09:30, without a time zone or a UTC offset. It combines a LocalDate and a LocalTime in one immutable object, with a precision of nanoseconds.
We use LocalDateTime when the place is known from the context, for example an appointment at a clinic, the opening of a shop sale, or a train departure printed on a ticket. To record the moment when something happened, such as a payment or a log entry, we use Instant or OffsetDateTime, because a LocalDateTime alone does not identify a point in time.
The following example books a dental appointment and runs the common LocalDateTime operations on it.
LocalDateTime appointment = LocalDateTime.of(2026, 5, 14, 9, 30); // 2026-05-14T09:30
LocalDateTime nextSlot = LocalDateTime.parse("2026-05-14T11:00"); // 2026-05-14T11:00
LocalDateTime reminder = appointment.minusHours(24); // 2026-05-13T09:30
boolean earlier = appointment.isBefore(nextSlot); // true
long gapMinutes = Duration.between(appointment, nextSlot).toMinutes(); // 90
String shown = appointment.format(DateTimeFormatter.ofPattern("dd MMM yyyy, hh:mm a", Locale.US)); // "14 May 2026, 09:30 AM"
ZonedDateTime inBerlin = appointment.atZone(ZoneId.of("Europe/Berlin")); // 2026-05-14T09:30+02:00[Europe/Berlin]
The last line is the step that turns the local value into a real moment, by attaching the zone of the clinic. We start with creating, parsing, formatting and modifying values. We also compare values, convert them to zoned types, where daylight saving gaps matter, and list the cases where LocalDateTime is the wrong choice.
1. What LocalDateTime Holds and What It Lacks
A LocalDateTime holds a LocalDate part and a LocalTime part, and nothing else. The value 2026-05-14T09:30 is 09:30 in Berlin for a clinic in Berlin and 09:30 in Chicago for a clinic in Chicago, which are two moments seven hours apart. So a LocalDateTime is not an instant on the timeline until we add a zone.

The toString() method prints the ISO-8601 form with a T between the date and the time, such as 2026-05-14T09:30:15.500. Seconds and fractions are printed only when they are not zero. Like all java.time classes, LocalDateTime is immutable and thread-safe.
2. Creating LocalDateTime
We get a LocalDateTime from the clock, from separate numbers, or by joining a date with a time. The class has no public constructor, so all three ways use static factory methods.
2.1. Current Date and Time
The now() method reads the system clock and converts it to the default time zone of the JVM. To get the local date and time of another place, we pass its ZoneId.
LocalDateTime now = LocalDateTime.now(); // current date-time, default zone
LocalDateTime utcNow = LocalDateTime.now(ZoneId.of("UTC")); // current date-time in UTC
LocalDateTime fixed = LocalDateTime.now(Clock.fixed(Instant.parse("2026-05-14T07:30:00Z"), ZoneId.of("Europe/Berlin"))); // 2026-05-14T09:30
The last line passes a fixed Clock, which makes the value repeatable in unit tests. The full set of ways to read the clock is in getting the current date and time.
2.2. Create LocalDateTime With Values
For fixed values, the of() method takes the year, month, day, hour and minute, and optionally the second and the nanosecond. The month is a number from 1 to 12 or a Month constant, and we write the numbers without leading zeros, because 09 is an invalid octal literal in Java.
LocalDateTime minutes = LocalDateTime.of(2026, 5, 14, 9, 30); // 2026-05-14T09:30
LocalDateTime seconds = LocalDateTime.of(2026, Month.MAY, 14, 9, 30, 15); // 2026-05-14T09:30:15
LocalDateTime nanos = LocalDateTime.of(2026, 5, 14, 9, 30, 15, 500_000_000); // 2026-05-14T09:30:15.500
LocalDateTime bad = LocalDateTime.of(2026, 4, 31, 9, 30); // DateTimeException: Invalid date 'APRIL 31'
2.3. Combine LocalDate and LocalTime
When a form has separate date and time fields, we build each part and join them. The atTime() method on LocalDate and the atDate() method on LocalTime do the same thing from the other side.
LocalDate date = LocalDate.of(2026, 5, 14);
LocalTime time = LocalTime.of(9, 30);
LocalDateTime joined = LocalDateTime.of(date, time); // 2026-05-14T09:30
LocalDateTime fromDate = date.atTime(time); // 2026-05-14T09:30
LocalDateTime fromTime = time.atDate(date); // 2026-05-14T09:30
LocalDate datePart = joined.toLocalDate(); // 2026-05-14
LocalTime timePart = joined.toLocalTime(); // 09:30
3. Parsing a String to LocalDateTime
The parse(CharSequence) method reads the ISO format 2026-05-14T09:30:15, with the letter T and optional seconds and fractions. For text in another layout, we pass a DateTimeFormatter built from a pattern.
For a 12-hour time with AM or PM, the pattern must use hh together with a, and the formatter needs a Locale. With HH (0-23) and a, the text “02:15:30 PM” throws a DateTimeParseException, because hour 2 conflicts with PM.
LocalDateTime iso = LocalDateTime.parse("2026-05-14T09:30:15"); // 2026-05-14T09:30:15
DateTimeFormatter withSpace = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime spaced = LocalDateTime.parse("2026-05-14 14:15:30", withSpace); // 2026-05-14T14:15:30
DateTimeFormatter twelveHour = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a", Locale.US);
LocalDateTime pm = LocalDateTime.parse("2026-05-14 02:15:30 PM", twelveHour); // 2026-05-14T14:15:30
DateTimeFormatter mixed = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a", Locale.US);
LocalDateTime conflict = LocalDateTime.parse("2026-05-14 02:15:30 PM", mixed); // DateTimeParseException: Conflict found: Field AmPmOfDay 0 differs from AmPmOfDay 1
Input from users, CSV files and APIs often arrives in an unexpected format, so production code catches DateTimeParseException and reports the bad value. Converting a String to LocalDateTime lists more patterns and the common parse errors.
4. Formatting LocalDateTime for Display
The format(DateTimeFormatter) method writes the value in any pattern. A common request is to remove the T from the default output, which needs a pattern with a space in its place.
LocalDateTime visit = LocalDateTime.of(2026, 5, 14, 14, 5, 9);
String isoText = visit.toString(); // "2026-05-14T14:05:09"
String noT = visit.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // "2026-05-14 14:05:09"
String us = visit.format(DateTimeFormatter.ofPattern("MM/dd/yyyy h:mm a", Locale.US)); // "05/14/2026 2:05 PM"
String french = visit.format(DateTimeFormatter.ofPattern("d MMMM yyyy HH'h'mm", Locale.FRANCE)); // "14 mai 2026 14h05"
Text inside single quotes, such as ‘h’, is printed as it is. All pattern letters are explained in the DateTimeFormatter guide, and more output formats are in formatting LocalDateTime.
5. Modifying LocalDateTime
Every change returns a new object, so we assign the result to a variable. The plus and minus methods move the value by an amount, the with methods replace one field, and truncatedTo() clears the smaller fields.
| Change | Methods |
|---|---|
| Add an amount | plusYears(), plusMonths(), plusWeeks(), plusDays(), plusHours(), plusMinutes(), plusSeconds(), plusNanos(), plus(Duration) |
| Subtract an amount | The same names with minus, such as minusDays() and minusMinutes() |
| Replace a field | withYear(), withMonth(), withDayOfMonth(), withHour(), withMinute(), with(TemporalAdjuster) |
| Drop smaller fields | truncatedTo(ChronoUnit.HOURS) and other units up to DAYS |
LocalDateTime start = LocalDateTime.of(2026, 5, 14, 23, 30);
LocalDateTime threeHoursLater = start.plusHours(3); // 2026-05-15T02:30, next day
LocalDateTime earlier = start.minusMinutes(45); // 2026-05-14T22:45
LocalDateTime nextYear = start.plusYears(1); // 2027-05-14T23:30
LocalDateTime morning = start.withHour(8).withMinute(0); // 2026-05-14T08:00
LocalDateTime hourStart = start.plusMinutes(20).truncatedTo(ChronoUnit.HOURS); // 2026-05-14T23:00
Unlike LocalTime, a LocalDateTime carries the overflow into the date, so 23:30 plus three hours moves to the next day. Adding and subtracting hours, minutes and seconds has more examples, including the same methods on ZonedDateTime.
6. Comparing Values and Measuring the Gap
Two values compare on the local timeline with isBefore(), isAfter() and isEqual(), and Duration.between() or ChronoUnit measures the time between them. These results are correct only when both values belong to the same zone, as two appointments in one clinic do, and no daylight saving change lies between them.
LocalDateTime first = LocalDateTime.of(2026, 5, 14, 9, 30);
LocalDateTime second = LocalDateTime.of(2026, 5, 15, 11, 0);
boolean before = first.isBefore(second); // true
boolean equal = first.isEqual(LocalDateTime.parse("2026-05-14T09:30")); // true
Duration gap = Duration.between(first, second); // PT25H30M
long hours = ChronoUnit.HOURS.between(first, second); // 25
boolean sameDay = first.toLocalDate().equals(second.toLocalDate()); // false
To compare only the calendar day, we compare the toLocalDate() parts, as in the last line. Comparing two LocalDateTime instances covers sorting and compareTo() in detail.
7. Converting LocalDateTime to ZonedDateTime and Instant
A LocalDateTime becomes a moment only after we tell Java where it applies. The atZone(ZoneId) method returns a ZonedDateTime, atOffset(ZoneOffset) returns an OffsetDateTime, and both have toInstant().
LocalDateTime local = LocalDateTime.of(2026, 5, 14, 9, 30);
ZonedDateTime zoned = local.atZone(ZoneId.of("America/Chicago")); // 2026-05-14T09:30-05:00[America/Chicago]
OffsetDateTime offset = local.atOffset(ZoneOffset.UTC); // 2026-05-14T09:30Z
Instant instant = zoned.toInstant(); // 2026-05-14T14:30:00Z
long epochSecond = local.toEpochSecond(ZoneOffset.UTC); // 1778751000
LocalDateTime fromInstant = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Kolkata")); // 2026-05-14T20:00
Some local times do not exist in a zone, because the clocks jump forward when daylight saving time starts. On 2026-03-08 in New York, the clocks go from 01:59:59 to 03:00, so 02:30 never happens. The atZone() method does not throw in that case. It moves the time forward by the length of the gap.
LocalDateTime inGap = LocalDateTime.of(2026, 3, 8, 2, 30);
ZonedDateTime shifted = inGap.atZone(ZoneId.of("America/New_York")); // 2026-03-08T03:30-04:00[America/New_York]
Conversions to the legacy classes go through Instant or through java.sql.Timestamp, as shown in converting between Date and LocalDateTime. The zone conversions are covered in LocalDateTime and ZonedDateTime.
8. When to Use LocalDateTime and When Not To
A dental clinic app shows a good fit. The clinic is in Berlin, and a patient books 09:30 on 14 May. We store the appointment as a LocalDateTime plus the clinic’s zone ID, because the appointment stays at 09:30 local time even if Germany changes its daylight saving rules before that date. When we send the reminder, we combine both and get the exact moment.
record Appointment(String patient, LocalDateTime start, ZoneId clinicZone) {
Instant reminderAt() {
return start.atZone(clinicZone).minusHours(24).toInstant();
}
}
Appointment visit = new Appointment("Lokesh", LocalDateTime.of(2026, 5, 14, 9, 30), ZoneId.of("Europe/Berlin"));
Instant sendAt = visit.reminderAt(); // 2026-05-13T07:30:00Z
A LocalDateTime is the wrong type for anything that records when an event happened, such as a log entry, a payment or a created-at column. Two servers in different zones would write different values for the same moment, and the values cannot be compared. For those fields, we use Instant or OffsetDateTime, as ZonedDateTime vs OffsetDateTime explains.
9. Java LocalDateTime FAQs
Searches about LocalDateTime often come from serialization errors and time zone confusion.
9.1. What Is the Difference Between LocalDateTime and ZonedDateTime?
A LocalDateTime is a date and a time with no zone, so it does not identify a moment. A ZonedDateTime adds a ZoneId and the resolved offset, so it maps to one instant and follows daylight saving rules when we do arithmetic.
9.2. Does LocalDateTime Store a Time Zone?
No. Even LocalDateTime.now(ZoneId.of(“Asia/Tokyo”)) uses the zone only to read the clock, and the returned object does not remember it. We keep the zone in a separate field or use ZonedDateTime.
9.3. How Do We Fix “Java 8 date/time type java.time.LocalDateTime not supported by default”?
Jackson 2.x throws this InvalidDefinitionException when the jackson-datatype-jsr310 module is missing. We add the dependency and register JavaTimeModule on the ObjectMapper, and we disable WRITE_DATES_AS_TIMESTAMPS to get ISO text instead of a number array. Spring Boot registers the module for us, and Jackson 3 includes java.time support in jackson-databind. Jackson date handling shows the full setup.
9.4. How Do We Convert Epoch Milliseconds to LocalDateTime?
We create an Instant and pick the zone in which the local value should be read.
LocalDateTime fromMillis = LocalDateTime.ofInstant(Instant.ofEpochMilli(1778751000000L), ZoneOffset.UTC); // 2026-05-14T09:30
9.5. Can We Store LocalDateTime in a Database?
Yes, in a column without a time zone, such as TIMESTAMP in PostgreSQL or DATETIME in MySQL. JDBC 4.2 drivers accept it through setObject(). For event timestamps, a TIMESTAMP WITH TIME ZONE column and OffsetDateTime or Instant are the safer pair.
10. Conclusion
A LocalDateTime is a date plus a time with no zone, so it describes what a wall clock and a calendar show in a place we already know. Values come from of(), parse() or LocalDate.atTime(), and we pair hh with a and a Locale for 12-hour text.
Arithmetic and comparisons work on the local timeline, which is correct inside one zone. The atZone() method turns the value into a real moment and shifts times that fall into a daylight saving gap. The java.time tutorial index links the other date and time classes.
11. References
- LocalDateTime (Java SE 25 API)
- DateTimeFormatter (Java SE 25 API)
- ZonedDateTime (Java SE 25 API)
- Jackson modules for Java 8 (GitHub)
Happy Learning !!