Re-Evaluating Kotlin

Published at 15:40 on 15 December 2025

Back in 2019, I wrote:

The pity is that once one does things other than Android software development in Kotlin, the rough edges in its ecosystem quickly become all too apparent. Just out of curiosity I’ve been playing with the Ktor server-side framework. The documentation ranges from flat-out obsolete (and thus incorrect) to simply nonexistent. The result is that even simple things take hours of tedious experimentation to determine how to do.

I’m hoping that Android development goes better, but unless those rough edges get smoothed out, and soon, Kotlin may well end up being stereotyped as an Android-only thing.

Only last month, I wrote:

The big problem with Java is not the language, it is the culture around the language. I have a friend with a number of pet sayings, one of which is “tradition is mightier than innovation,” and that certainly applies in spades to Java. There’s just so much bad tradition enshrined as respected convention in the Java world. I call the result Java Community Antipatterns or JCA’s for short.

      ⋮

If it weren’t for the JCA’s, Kotlin would be a near-ideal programming language. If it weren’t for the heat and dryness, Phoenix would have a near-ideal climate. (And aside from that, Mrs. Lincoln, how did you enjoy the play?)

But, I ended up concluding:

The Java community’s faults may be legion, but Java set out to be a “write once, run anywhere” language, and its virtual machine has to this date succeeded at that goal better than any other such environment of which I am aware.

So I’m back to coding in Kotlin, and once again Kotlin’s nemesis becomes clear. It is its proximity to the Java universe, and by implication the universe of JCA’s.

The Java virtual machine’s advantages make the concept of a language like Kotlin tempting. It is why JetBrains decided to create Kotlin. It is why I have been coding in Kotlin.

Unfortunately, realizing that concept is a very tall order. Quite simply, it is not exactly easy to take a complicated, badly-designed, antiquated ecosystem and attempt to layer a more rational, more modern, more well-designed one on top of it. This problem becomes all the more acute if said ecosystem is associated with a culture that has enshrined harmful antipatterns as part of its respected traditions. The Kotlin development team is obviously trying as hard as they can, but it doesn’t matter: their effort still falls short.

I am picking up a project I put aside about five months ago, due to a need then to focus on other more pressing goals. Then, I had foundered for some time on serialization. Kotlin has what is in theory a great serialization subsystem. Of course, given the Java world it was layered on, it took no small effort to implement it. And the latest version of Kotlin at the time hadn’t quite got its implementation right; I was getting bitten by those bugs, which were causing my code to throw exceptions and die. JetBrains was aware of those issues, and planning to fix them in the future, but that didn’t help me in the here and now. Eventually, after blowing several days on the issue, I found the magic combination of an older Kotlin compiler and serialization library that did not make these bugs manifest.

That resolved my issue, but enough time had transpired that when I recently resumed my efforts, one of the things at the top of my agenda was to upgrade to the current versions of things and see if JetBrains had fixed the bugs. I did, and they had. So far, so good.

The problem is, the build system (based on the Java universe’s Gradle, which coming from the Java world is the standard shambolic mess you find in that world) has now for some reason started producing a corrupt Jar file. The jar command-line utility can list and process the Jar file. It can locate the class I am trying to invoke. Yet when I attempt to invoke it, the JVM claims it cannot find the class.

So now I have to troubleshoot that build system and figure out why it is (once again) failing me. And this is the sort of shit that keeps coming up in the Kotlin world, simply due to its proximity to the Java one.

Where this goes, I am not sure. Maybe I will find a resolution or a workaround, like I did the last time the build system started spitting out Jar files that were corrupted in a slightly different way. Maybe not. Maybe I will just give up on Kotlin, despite its advantages, because the disadvantages simply outweigh them.

Cutover Complete

Published at 13:17 on 19 November 2025

If you can read this, it means the site cutover is (hopefully) complete. This site is now being hosted by a server in Canada, in a server farm being run by a non-US (French) entity.

The DNS name service (for you non-geeks, that is what makes the blackcap.name part of this site’s address work) has yet to be cut over, but that is because there is about a year left on my existing contact, and I am being cheap. I already have an existing business relationship with a Canadian name registry, and if things start going south rapidly it won’t be terribly hard to cut that over.

Given the current state of affairs, I think it best remove as many dependencies on US-based organizations as possible. This is not exactly a prominent blog, but still better safe than sorry. With authoritarian bastards, you never know.

Portable GUI Framework Principles

Published at 08:25 on 17 November 2025

There are two major schools of thought when it comes to portable graphical user interface frameworks:

  1. To create applications that harmonize as well as possible with the rest of the platform an application happens to be running on.
  2. To create applications that appear as alike as possible, no matter what platform an application happens to be running on.

It is my contention that, for desktop applications, the first principle is the correct one, and the second principle is a harmful design anti-pattern.

The reason is quite simple: most users use one sort of platform, and that is it. You have Windows users, Mac users, and Linux users, and those users tend to stick with their desktop platform of choice. So they don’t care how a given application looks on some other platform. All they care about is how well it harmonizes with the rest of the platform they do use. This makes the basic rules of the game the same no matter what application they happen to be using. I don’t care how special you think your own application is, so far as the average user is concerned, it’s just another tool in their toolbox.

And it is the users, not the developer, who is the truly important ones here. The users are the ones for whom the application was written, after all. They vastly outnumber the developers.

Yes, some users switch between platforms of different types. That is their choice. As part of their choice, they have accepted the natural consequence of having to deal with multiple pattern languages. There is no way to be such a user and to not have this consequence imposed on one. As such, an application whose look and feel varies from platform to platform does not impose any significant new onerous cost on such users.

I am speaking here about desktop applications. For smartphone applications, the matter is quite different. This is because, despite all the hullabaloo about which is best, the Android look and feel really does not differ that much from the iPhone look and feel. It is one of the things that really struck me when I moved from Android to iPhone. Yes, there were a few places where I got confused and had to get used to doing things “the iPhone way,” but surprisingly few. For the most part, I was able to just pick up my new iPhone and start using it.

Since the pattern languages of the two smartphone platforms are so similar, it is sensible to have a goal of a smartphone app appearing as alike as possible on both platforms. It makes the jobs of your documentation writers and user-support people easier, and it does so at approximately zero cost to any end user.

It is even less appropriate to have a goal of making a smartphone app and a desktop application appear as identical as possible than it is to have such a goal for two different desktop platforms. This is because the two basic types of platform are so radically different. A smartphone is profoundly resource-deprived compared to a desktop system. The desktop can be an appropriate place to host large, complex applications (assuming large, complex things need to be done). A smartphone is never an appropriate place for such an application.

What this all means is that frameworks like Avalonia which prioritize Principle No. 2 above should be seen as primarily smartphone frameworks that also happen to support the desktop… badly.

Alternatives to the JVM for Portable Desktop Applications

Published at 19:17 on 16 November 2025

I have been using the JVM (Java Virtual Machine) to host desktop applications I develop. Originally I wrote the code in Java, but in recent years have switched to Kotlin, because it is a more modern language with a more concise and expressive syntax and a more sensibly-designed standard library.

The big problem with Java is not the language, it is the culture around the language. I have a friend with a number of pet sayings, one of which is “tradition is mightier than innovation,” and that certainly applies in spades to Java. There’s just so much bad tradition enshrined as respected convention in the Java world. I call the result Java Community Antipatterns or JCA’s for short.

Kotlin is better in the JCA department, but due to its intellectual proximity to the Java world, some of the Java brain rot has inevitably bled over, so Kotlin still has its issues. To pick just two examples:

  • Its Ktor Client HTTP request library is a lot more complex than it ought to be (way more complex than the Requests library that is common in the Python world or the standard System.Net.Http package of the .NET world). Despite the complexity, some of its features, such as bearer token management, still manage to fall short of what is commonly needed.
  • Its lightweight multithreading is likewise (there are both Job and Deferred objects, both very similar yet subtly different, where .NET gets along just fine with a single Task object).

On top of that, for a lot of things, there simply isn’t a Kotlin library. You end up calling a Java library. That is easy enough to do, because Kotlin runs on the JVM and was designed to interoperate with Java code, but the Java library is inevitably a lot clunkier and harder to use than it ought to be, due to those JCA’s.

If it weren’t for the JCA’s, Kotlin would be a near-ideal programming language. If it weren’t for the heat and dryness, Phoenix would have a near-ideal climate. (And aside from that, Mrs. Lincoln, how did you enjoy the play?)

So I decided to kick the tires on potential alternatives yet again. Always a good idea, because the state of the art is always in flux in the computing world. And the answer I got was: despite the flaws of the JVM, it’s still hard to do better than it.

Mostly, it boils down to three things the Java world got right:

  • To prioritize platform agnosticism.
  • To include graphical user interface (GUI) capability in the core framework.
  • To prioritize, or at least facilitate, making the graphical elements in Java GUI programs harmonize with the overall pattern language of the platform the application happens to be executing on.

The first two encapsulate the “write once, run anywhere” philosophy that has been one of Java’s key design principles basically since Day One. Other virtual machines still just don’t do as good a job of actualizing this principle.

This most often manifests when graphical desktop applications are involved. Pretty much any virtual machine out there will do a great job of running command line utilities or daemons running as detached jobs portably.

Python, for example, has a great cross-platform GUI library called PyQt. Alas, it doesn’t ship with it; one must add it on. And, like many Python libraries, it isn’t written purely in Python. In fact, it’s mostly written in C++, a language that compiles down to machine code, not portable byte code. This makes it a lot harder to distribute a run-anywhere application, particularly on platforms like the Mac, which is unusually programmer-hostile in this regard.

Microsoft .NET has a very nice virtual machine, with a standard library that, unlike Java’s, is for the most part well-designed and easy to use. But it was written by Microsoft, whose corporate interests as the creator of Windows run counter to the ideal of platform agnosticism. .NET code can run (and long has been able to run) on Macs and Linux boxes… as long as you stick to command-line or daemon programs. Out-of-the-box desktop support is no longer strictly limited to Windows (Macs are now supported), but Linux is left out in the cold even as of this late date.

There are third-party frameworks like Avalonia that claim to address this deficiency, but by not being present out-of-the-box, they raise the same gotchas that PyQt does in Python. Plus most of them fail badly when it comes to harmonizing well with the overall pattern language of a platform.

What it all boils down to is that I could shift to some alternate platform, and this would make my life easier in some respects, but only at the cost of making it significantly harder in others, or inescapably compromising the quality of my applications. It is far from clear to me that there would be any overall net benefit. In fact, I rather suspect the opposite would be the case. I guess that’s good news in a sense, as it means I probably haven’t been wasting my time using a suboptimal platform.

The Java community’s faults may be legion, but Java set out to be a “write once, run anywhere” language, and its virtual machine has to this date succeeded at that goal better than any other such environment of which I am aware.

Newspapers and Magazines Are Not Timelines

Published at 07:45 on 2 November 2025

Time to clarify what I recently published here.

Per my recent definition, newspapers and magazines might appear to be timelines, but they are not. This is because all articles in a publication have a single source: the the individual (or typically) firm producing the publication. Everything goes through the same editorial team before it gets in. The information has been curated by humans.

The exception would be a publication with extremely lax (or no) editorial standards whatsoever, which simply publishes everything (or nearly everything) submitted to it. Those would be timelines.

This also explains why the posts of an individual social media account are not timelines, even though virtually all social media users repost content from others. Those reposts were still done by a human. The information has still been curated.

Thesis: Timelines Are Evil

Published at 07:41 on 31 October 2025

Before continuing, it is necessary to define what I mean by timeline in this article.

timeline, n. An online list of one-to-many communications from mixed sources.

So, Facebook’s infamous algorithmic timeline qualifies as a timeline, but so are its “feeds” of friends and groups. The chronological timelines of Bluesky and Mastodon are also timelines, and therefore also evil. An email account that is on one or more mailing lists is also a timeline, but an email account that is not subscribed to lists is not a timeline. If you log onto Facebook, the list of your friends is not a timeline, because that is a list of Facebook accounts, not communications from those accounts. If you click on a friend and view their posts, that is also not a timeline, because the contents come from a single source, not mixed sources. And so on.

Timelines are evil because of the time burden they impose. This is because of how computer technology makes it so easy to send information, coupled with how timelines often contain many senders of information, inevitably makes for very busy timelines.

Some very timeline-like things existed before the dawn of the Internet. Junk mail and junk phone calls turned physical mailboxes and telephones into such things. This is why so many people rightly found them objectionable.

Algorithmic timelines are more evil than strict chronological ones, because of the opaque nature of the criteria for ordering and selecting timeline contents, but even strict chronological timelines are evil.

The only thing that can make a timeline non-evil is sparse traffic, but due to information being so cheap and easy to send this can never reliably be the case. Evil is the natural state of most timelines, and even normally non-evil timelines will at times assume this state.

Timelines are the chief thing responsible for making people spend so much time online and disconnected from the real world that exists outside of cyberspace. Create a timeline for someone, and the fear of missing out on something important that might be buried in it leads them to spend unhealthy amounts of time online.

As such, timelines are probably responsible (or at least partly responsible) for much of the recent trend of politics and society getting worse, which is driven by organic and real-world interactions being replaced by time spent in cyberspace, based on opaque criteria, all the while being monitored and exploited by capitalists and politicians.

At least this is my current operating theory. I arrived at it as a result of struggling over why I spent so much time in front of computer screens, to the detriment of achieving other goals in my life. As such, I am now in the process of experimentally de-timelining my life.

A Little Cheesy, but Not Terrible

Published at 07:46 on 21 September 2025

That’s my executive summary of Apple’s new Liquid Glass theme in macOS 26 and iOS 26.

Sure, it was needless make work for Apple’s design department. Sure, it would have probably been better off not to spend all that effort. However, I haven’t run across anything bad overall. I was ready to follow this suggested list of settings tweaks, but quickly realized the default settings were just fine.

The minor changes in the system itself are nothing like the major, revision-to-revision changes in Apple’s mail client. Those were genuinely annoying, and it was hard to turn them all back. There was this persistent trend of using more and more screen real estate to display less and less useful information about each message. At least this is how things were five or six years ago; I have been using Mozilla Thunderbird ever since then.

It is Thunderbird’s lack of frequent, gratuitous design changes which generally makes for a generally better overall user experience. It may look a little dated, but you can see a lot more useful information about the messages in your inbox at a glance. The worst thing about Thunderbird is the message-composition editor, which has always been a little janky when one tries to do anything more than the most basic of HTML formatting, but putting up with that has been a price worth paying for some useful UI stability and density of information presentation.

Time and Date Done Right

Published at 22:15 on 17 June 2025

To avoid being simply negative and cutting others’ efforts down, I will contrast the shambolic time and date situation in Java with the superior one in Python.

The latter has two time and date packages. This is largely as a result of history. One, time, came early on, and basically is a way to call the Posix time and date functions from Python. The other, datetime, came later, and is geared to more advanced use cases.

I almost always just use time, because as I said earlier, by far the most common uses for a time datatype are to create an instance of one and to print it. For that, the traditional time library is more than sufficient.

But even datetime is so much better designed than its Java counterpart. Instead of nine absolute time types, there are three (a calendar date, a time of day, and a date with a time). Instead of two relative types, there is just one (which, really, is all one needs). Instead of an elaborate set of classes devoted to formatting and outputting a time, there is just a strftime method for each time object (and all the strftime‘s use exactly the same format-specifying mini-language, which is mostly compatible with the one used in the Posix/C world. The cognitive load of understanding it all is so much more reasonable.

As for the legacy class, its base time type is a Python floating point number. This is a value type, and as such avoids all the headaches associated with a mutable reference type. Its alternate time type is a Python tuple. Since tuples are immutable in Python, this also neatly avoids mutability headaches. And since standard, pre-existing data types are used, there is once more less cognitive load imposed on the programmer.

Since there is nothing fundamentally wrong with the older time class, it is considered just fine to use it in new programs. Because of course it is: it is the simpler of the two, and simplicity has long been a core value in the Python world.

It’s one of the reasons I can be so harsh on Java. They knew better, or rather should have known better. Python predates Java, and Python did not come up with this simplicity-as-a-virtue business. UNIX embodied it over two decades before Python popped onto the scene. By the time the 1990’s rolled around, UNIX had a longstanding reputation for being an exceptionally productive programming environment.

The principles were known. The Java world chose to ignore them, and has suffered as a result ever since.

Site Instability Update

Published at 14:27 on 15 June 2025

This site has been stable since I implemented a stopgap measure to prevent abusive crawling from taking it down.

Alas, the abusive crawling persists. It is happening in spite of robots.txt directing robots to avoid the troublesome URL. The robots in question also, contrary to best practice, contain no user agent information identifying themselves. Pure sleaze. I suspect one or more AI firms to be behind it, trolling the Internet for code to train their models on.

So a longer-term fix has now been implemented. The offending URL now always returns and HTTP 403 (Forbidden) response, about which RFC 2616 says “The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.”

The returned response contains instructions for how to edit the URL in the browser URL window so as to reach the desired service, so humans once more have access to it. Effectively, this functions as a form of CAPTCHA.

It’s done via a CGI script. It doesn’t have to be, but it was easy that way. Rename the old script to the name that now accesses the service, and install the new one as a drop-in replacement for the old one at the original address. Plus, it keeps the URL similar so it’s relatively easy for humans to edit into the form needed to access the service.

The “script” is actually a compiled C program and not a script, because it has to be. Anything more resource-intensive might cause the abusive crawlers to take my site down.

Anyhow, problem (hopefully) solved.

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.