Why I Hate Java, Chapter No. Too-High-to-Count
Published at 10:31 on 13 June 2025
The time and date situation. Specifically, the new “improved” one.
Yeah, I get it. The old one is full of deprecated legacy cruft calls. Its fundamental time type is mutable (which is undesirable when writing multithreaded code). Plus it has an absolutely horrible name: Date refers to an object that holds the number of milliseconds since the UNIX time epoch.
On that latter point, I have no idea why the early Java dev team chose such a lousy name. They really had no excuse. About eight years prior, the Posix team chose time_t for their analogous data type. That had only one-second resolution so they later came up with a struct timeval with microsecond resolutions. Both are fundamentally reasonable name choices. Calling a time a time makes just so much more sense than calling a time a date. Just goes to show how long the brain rot has been prevalent in the Java world.
Speaking of that brain rot, Baeldung is one of the go-to sources for information in the Java world and their article on the new time and date API is a total hoot. It’s just one whopper after another. Let’s pick out a few of them, shall we?
- Working with dates in Java used to be hard.
- No, it didn’t. Not really, once you got over the weird naming convention of calling a time a date, and the fact that there were two classes for formatting a time. Because of course there were. This is Java, and we can’t do anything right. Got to write one weak, lame API and then a second one (also limited) to make up for the weakness and lameness of the first.
- These were only suitable for the most basic tasks.
- Bullshit, they were suitable for virtually every routine task. Only a tiny percentage of programs delved into the minutiae of dates, times, time zones, and calendars enough for the standard library to be insufficient, and that was not reason enough to clutter up that standard library with support for rarely-needed edge cases. It’s a reason for a third-party library of extended, specialized time and date functions to exist.
- A first advantage of the new API is clarity…
- Bwahahahahahahahahaha! This whopper is so bad I could write several paragraphs about just how much of a whopper it is.
And now I shall write those paragraphs.
The new library has no fewer than nine absolute and two relative time classes. No, I am not making this up. They all differ in various subtle ways from each other, ways that will require one to spend no small amount of time reading documentation to figure it all out. And figure it all out you must, or your code will throw fatal exceptions and fail to run if you use the wrong class in the wrong place. This is apparently what Java-heads consider “clarity,” and feel so strongly that it is such that they set the term in boldface.
And that’s just times. Then there’s time zones. There are two time zone classes, again because of course they are. (Making three types of time zone in total once you add the legacy one which is still around. Oh, wait, I also forgot that there are now time zone ID objects, which differ from actual time zones in various subtle ways.) The two new zone types also differ in various subtle ways, and you have to study the documentation a while to understand these differences, too.
The most common uses by far for a time object are to create it and to print it. Manipulations are so far down on the list that they can be disregarded for purposes of discussing the most common uses. Let’s print the date in the preferred format for the current locale. For the old Date option, it is relatively simple:
Date now = new Date(); DateFormat formatter = DateFormat.getDateTimeInstance(); System.out.println(formatter.format(now));
After a lot of reading, eventually one figures out that the Instant (you might think they would call it Time or TimeStamp, but apparently some types just never learn, sigh) class is the closest analogue to the old Date class, and the new formatting class is DateTimeFormatter. Great!
Instant now = new Instant(); DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(); System.out.println(formatter.format(now));
But that won’t even compile. Turns out you can’t construct an Instant, the constructor is now private. You must call the now static method:
Instant now = Instant.now(); DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(); System.out.println(formatter.format(now));
Nope, still lose. Turns out formatting has been dumbed down. Formatters are now too stupid to come up with a preferred default style. You must tell it the style. To do that, you have to learn about (and introduce) an extra style-specifying class.
Instant now = Instant.now(); DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG); System.out.println(formatter.format(now));
Yay! It compiles! Uh oh, it throws a weird exception when running.
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: MonthOfYear at java.base/java.time.Instant.getLong(Instant.java:604) at java.base/java.time.format.DateTimePrintContext$1.getLong(DateTimePrintContext.java:205) at java.base/java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:308) at java.base/java.time.format.DateTimeFormatterBuilder$TextPrinterParser.format(DateTimeFormatterBuilder.java:3363) at java.base/java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2402) at java.base/java.time.format.DateTimeFormatterBuilder$LocalizedPrinterParser.format(DateTimeFormatterBuilder.java:4848) ...
Now you get the fun of delving into the Temporal interface and temporal accessors. Which it turns out doesn’t even help. The problem is not even there, it is someplace else. It turns out that the new formatter will accept arguments it is incapable of processing. There are nine date/time objects, remember? Which ones work at which times is a complicated set of rules. So not so fast! You have to take on the cognitive load of understanding all that too.
Eventually you hit on it. Time zones used to be part of formatting. Makes sense, right? A point in time is a point in time. What the clock shows for that point in time depends on your local time zone. But that’s too simple and logical for Java, so we’ve fixed it for you. Some edge cases might want to store times together with time zones. It’s easy enough to do that on one’s own (just define a class containing a time and a zone), but hey, let’s shove complexity at the programmer and support that edge case. It’s the Java way. Might as well support the notion of a time of day discombobulated from any notion of a zone, too, just in case someone might want that. As well as a whole batch of other possibilities. And since we want to format those objects, too, we can’t have a zone in the formatter. It must be in the time object itself. So we are passing the wrong sort of time object if we want to print out a local time with a zone.
After more studying yet, you finally determine that the one time of day class out of nine which we need is ZonedDateTime. Of course, Java being Java, you can’t just call the ZonedDateTime constructor with the Instant you wish to print. The constructor is again private and you must call a static method. And to top it all off, that static method is too stupid to have any notion of a default local time zone so you must fetch and furnish that as well. And at long last, you have arrived at the code to do what you want:
Instant now = Instant.now(); DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG); ZonedDateTime printable = ZonedDateTime.ofInstant(now, ZoneId.systemDefault()); System.out.println(formatter.format(printable));
Of course, most programmers never get that far. The ones whose eyes don’t glaze over after learning there are nine different time types (they’re in a hurry and don’t have time for this lunacy), are probably going to give up when they run into the UnsupportedTemporalTypeException getting thrown on them. Again, they just don’t have time for this crap. The boss is wondering when the new feature that sales promised the important new customer will be ready. Trusty old Date works just fine, doesn’t spring weird surprises on them, and isn’t even officially deprecated (and probably won’t be for a good long time).
And then you have hardcore Java-heads expressing frustration and mystification as to why so many programmers are continuing to use the legacy API. Gee, I dunno why that might be. It’s just a mystery.
And the problem is, it’s like this all over the place in Java. Gratuitous complexity everywhere. Code in Java, and the cognitive load you must contend with is at least an order of magnitude greater than it needs to be, if reasonable and sane software design prevailed in the Java world.