
Eleven years ago, I told my wife that we must embark on a voyage across the ocean to join the JCrete unconference. She took a dim view of this. “You mean, there is no program? You'll just stand around and drink beer and chat?” I exclaimed that this was no beer-quaffing odyssey but a quest for knowledge, and off we went. This year I was fortunate to embark on the quest again. Here are some things I learned.
If you, dear reader, have been at a conference, think about how it went. Maybe you listened to presentations that you could have watched online. You may have had regrettable food. And a few beers. That's not why you go, of course. You go because you can ask questions to the speaker after the talk. And discuss with fellow attendees in the hallway. And in the evening over beers, provided the music isn't too loud.
In an unconference, there are sessions. Every morning, aspiring presenters pitch their topics. You may be among them. And you get to vote on what happens. Sure, the sessions are ad-hoc and not as polished as a conference session. (Which you could watch online.) I consider that an asset. When I am confused, I can just interrupt the presenter without ruining a polished presentation, and get an instant answer. As well as the gratitude of other equally confused participants. And when I present, I get instant feedback on where I fall flat.
JCrete is particularly fortunate to have so many fantastic sessions that I can't possibly go to all the ones that I would like to attend. That's where the afternoon activities come in. At the beach or over olive oil tasting, I can approach a presenter and say “Hey, so sorry but I couldn't make your session, but how did it go?” Soon, the beach or olive grove recedes into the background as other participants join the discussion.
This repeats in the evenings. Mercifully, no ear-splitting music. Just the Cretan crickets. But they stop at sundown.
The organizers do a great job mixing up senior gurus, mid-career engineers, and junior aspirants. Once, one of the latter ones told me that her manager questioned the modest expense, and I gasped. What better chance can anyone possibly have to interact with such a talented group? That junior engineer has since reached the next level, and the manager replaced by AI.
Of course, AI was on everyone's mind, and a good fraction of the sessions focused on it. I use AI every day for generating ideas and code snippets, but don't personally do the kind of work that lends itself to full automation. So it is always interesting for me to hear about what goes on in the trenches. Here some random things I picked up.
Even though I am not in the AI avantguarde, I am lazy enough that I don't want to code up basic boring stuff myself. And I figure, neither do students. In fact, I know that to be true because just about every CS professor complains that students just turn in AI generated solutions for their homework.
So, what should students learn these days, and how should they learn it? I convened a session, and here were some observations:
Most people use Python or R to analyze data, often in a notebook environment in which one can mix text, code, and images (often generated by the code).

But you can do the same in Java. There is a Java kernel for Jupyter notebooks that you can use to run Jypiter with Java in Google Colab, Github Codespaces, and so on. Sven Reimers gave a demo of his JTaccuino notebook, a JavaFX application that is more pleasant to launch and use than Jupyter.
For plotting, Python has matplotlib and R has the declarative ggplot2, based on “the grammar of graphics”. Java doesn't have anything nearly as robust. Sven is in the process of vibe coding a declarative Java plotting library, in the spirit of ggplot2. I am looking forward to it.
In Python, you use pandas to hold tabular data. I learned about https://dflib.org/, a strong Java contender for the same. Its creator, Andrus Adamchik, was at JCrete.

One of my favorite JCrete activities is the excursion to beautiful Balos. I rode with Rémi Forax who, when I complained about the absence of a command line parser in the core Java API, casually mentioned his ArgVester project.
It doesn't use annotations. Instead, you describe all your options with a single record, like this:
record Option(
Optional<String> worksheet,
java.util.List<String> ids) {
}
This is what I needed for a small script right after my return from Crete. In main I call
var argVester = ArgVester.create(MethodHandles.lookup(), Option.class);
var option = argVester.parse(args);
I get an instance of that record. If my program is invoked with
--worksheet fred.sheet id1 id2 id3
then option.worksheet is a non-empty Optional holding the string "fred.sheet", and option.ids holds the ids.
The source code fits in a single file, so I can just put it next to the script that I am writing.
The next time you face a scary bash script, just rewrite it in Java! I did just that, and I am so glad I did. Before, I was scared to touch it, but now that it is in Java, I understand it and I was able to add some instant improvements.
When I publish material for one online platform or another, I often need to process some JSON files that describe some deployment details. Make that a lot of JSON files. Often, the platform designers start out with a naïve scenario and don't think how that scales to book-length material. Then scripting becomes essential.
Did I mention that I do all my scripting in Java these days? That's why I am keen on JEP 540: Simple JSON API that is scheduled to appear in Java 28. Is it any good? I convened a session to check it out.
Why have a JSON library in the core API at all? Perhaps the Java team can use it to replace internal ad-hoc JSON usage? Or perhaps they like the scripting use case? Jackson is too heavyweight for dragging it into the core API or simple scripts.
What makes Jackson heavyweight? Streaming and data binding. JEP 540 doesn't deal with either. It is just a simple API for reading and creating JSON trees.
Interestingly, the API does not use a sealed family of records that would allow for pattern matching and deconstruction. There is simply a sealed interface JsonValue with subinterfaces JsonString, JsonNumber, JsonBoolean, JsonNull, JsonObject, and JsonArray. If you know the structure of your document, you can traverse a path like this:
JsonValue root = Json.parse("chapter4.json");
String title = root.get("courses").get(0).get("title").asString();
To analyze an arbitrary node, call
Map<String, JsonValue> children = root.asMap();
Then use the Map API to find keys and values. Determine the types of the values with instanceof tests or type patterns in a switch.
Creating a JSON document is a bit tedious since you need to first put values into lists and maps, and wrap primitives. For example:
JsonObject root = JsonObject.of(Map.of(
"title", JsonString.of("Control Structures"),
"description", JsonString.of(chapterDescription),
"courses", JsonArray.of(List.of(
JsonObject.of(Map.of(
"course_id", JsonNumber.of(id1),
"title", JsonString.of("Expressions, Statements, Semicolons"),
"description", JsonString.of(section1Description))),
...
))
));
I guess the JsonArray case is ok since one usually generates those lists programmatically. If it was up to me, I'd overload of for JsonObject so that one doesn't need a Map.
Note that you cannot build up a JSON object incrementally like you can in Jackson. In this API, JSON values are immutable.
That makes it difficult to transform JSON objects. I often need to read a JSON file and then produce one that is similar to the original. In Jackson, I just edit the objects. But with an immutable value, I need to copy the entire structure except for the parts that change.
I have a fair amount of experience doing that in Scala, rewriting immutable XML trees. The Scala XML library has a rudimentary RuleTransformer that I found insufficient. I wrote a few helper methods that, if I adapted them to JSON, could look like this:
JsonObject transform(BiPredicate<JsonValue, Path> condition, Function<JsonValue, JsonValue> mapper) JsonObject remove(BiPredicate<JsonValue, Path> condition)
Here Path describes the path from the root to the value, with each path element being a map key or list index.
We also noted that the Java Class File API has transformers for transforming code sequences, methods, or classes.
We discussed whether there is a more generic way of describing these tree transforms, and someone mentioned Haskell lenses. I found a Java equivalent. So far enlightenment has eluded me, but I can sense the potential.
CopyOnWriteArrayList.subList()Heinz Kabutz held a session on how to propose a fix for a baffling behavior of sublists of “copy on write” array lists (COWAL).
I have never needed such a list, and I couldn't imagine how it could be a source of bafflement, so I found this pretty interesting.
So, when a COWAL gets mutated, a copy is made. But if anyone is iterating over it while the mutation happens, their iterator keeps traversing the old snapshot, without throwing a ConcurrentModificationException (CME).
Now consider the subList method. It yields a “live” view of the underlying list. For example, you can call list.subList(0, n).clear() to remove the first n elements of list.
Now what if you iterate over the sublist, while the list is mutated. Do you iterate over the snapshot, without a CME? That would have been my intuition. But it's not what happens. The sublist iterator throws a CME when the original list is mutated.
The Javadoc is silent about the detailed behavior of sublists. For example, one would assume that a sublist of a random access list is also random access, but that's not a requirement.
Of course, one could reimplement the COWAL sublist to track snapshots. Would the Java team accept a PR doing that? We were not sure, since it seems such a narrow usecase.
For good clean fun, I asked Gemini about this issue. To my surprise and delight it referred me to Heinz' blog. It also knew that subList yields an instance of the inner class CopyOnWriteArrayList.COWSubList. I asked it to reimplement that, and it confidently produced something not very useful.
When I learned Scala around 2008, in the dark days of Java, I enjoyed pattern matching. It made sense as an alternative to object-orientation. In the right circumstances. In the functional world of Scala, the poster child use case was a list. It is either empty or nonempty. In the latter case, with an element and a tail—another list. In Java, it would look like this:
sealed interface List permits EmptyList, NonEmptyList {}
enum EmptyList implements List { INSTANCE; }
record NonEmptyList(Object element, List tail) implements List {}
Huh? No methods? That's where pattern matching comes in:
static int length(List l) {
return switch (l) {
case EmptyList _ -> 0;
case NonEmptyList(var _, var tail) -> 1 + length(tail);
};
}
In OO, you would use a polymorphic method instead:
sealed interface List permits EmptyList, NonEmptyList {
int length();
}
enum EmptyList implements List {
INSTANCE;
int length() { return 0; }
}
record NonEmptyList(Object element, List tail) implements List {
int length() { return 1 + tail.length(); }
}
What is better? For an open-ended hierarchy, the OO approach wins. Imagine adding another class to the hierarchy. In the list case, a plausible candidate is a fixed-size list backed by an array. With pattern matching, you would have to locate every pattern and add a new case. That clearly doesn't scale. Polymorphism is the right choice in that situation.
But with a sealed hierarchy, you know all implementations. Consider adding new functionality. With OO, you have to add a method to every implementing class—if you control them, which is a big if. With pattern matching, you just write a static method and match the known cases.
I did not know that this duality between polymorphism and pattern matching had been a subject of programming language theory. Rémi Forax set me straight and pointed me to the expression problem.

Are sealed hierarchies common? There is JSON, of course. Chris Kiehl's book on data-oriented programming has intriguing examples. But perhaps not enough to dethrone OOP and replace it with DOP. Each perspective is useful. Which Scala know twenty years ago when it pitched itself as being both OO and functional.
I was surprised to overhear several conversations about pattern matching in Java, when there was genuine confusion about these things. So I convened a session to pitch the mantra of “if it's sealed, go forth and match patterns”.
That wasn't a hard sell, so I had ample time for the second part of my agenda—to steer people away from using weird features of the switch syntax. Here is a particularly troubling example.
During the event, rumors swirled that Valhalla—support for flat value types in the JVM—was about to be merged into the mainline JDK. In a hack session on Valhalla, we built it one last time from its separate branch. (By the time you read this, it is part of mainline JDK 28.) I went over some slides explaining how one can look at flight recordings to observe how Valhalla alters memory allocation.
Marc Hoffmann has a nifty 3D shader implemented in Java, and he was hopeful that turning a 3D vector class into a value type would improve performance.
But the flight recordings showed no meaningful difference. It turned out that his computation produced a huge number of intermediate objects of some other class that tracked the ray tracing progress. And those were polymorphic, so Valhalla couldn't flatten them. He has since rewritten that part, and then good old fashioned escape analysis kicked in, scalarizing those objects without needing the help of Valhalla.
Right now, the sweet spot for a value class is:
Why 56 bits? A value object can still be null, so there needs to be a nullness flag, which right now takes up a byte.

Eventually there will be syntax for expressing that a field or array element is not null, but we aren't there yet.
JCrete takes place at the Orthodox Academy of Crete, which has embarked on an epic renovation project. We were wondering what would be finished first—the renovation or Valhalla. My money is on the OAC.

The day after JCrete, we organize an event for kids. This year, of course, it had to be about AI. Cassandra Chin prepared an activity from her book Phippy's AI Friend, using Scratch and and a neural network module for image recognition.
John Kostaras asked me to put something together about vibe coding. I felt I shouldn't just have the kids vibe code, but also learn to look at the code. And I was very worried about installing anything on their laptops because I had terrible experiences with random student laptops. Paraphrasing Tolstoy, all happy computers are alike, but each Windows machine is unhappy in its own way.
So I put something together using the impressive Snapcode online Java IDE. But I had to admit it was pretty boring.
The original plan was to combine this with another activity that Kaitlyn Hornbuckle and Cassandra Chin had previously given at Devoxx4kids, vibe coding games with a coding agent in VS Code and a 3D game toolkit. Good luck installing that on random student laptops, I thought.
But I was wrong. The volunteers did an awesome job helping students install the software, and the kids had a blast vibe coding crazy games. As far as I can tell, they never looked at the generated code. But most of them were very young, and I don't think they would have gotten anything out of my more earnest activity. And for all I know, nobody will look at code in the future anyway.

I am sure every JCrete attendee went home with their own list of things that they want to explore and do once they get home and regain some time.
Here a few miscellaneous things that I picked up and want to look at further.
@Measurement(iterations = x, time = y) @Fork(z).Now that I am back home, I think of the great memories and the long todo list. My sincere thanks go to the volunteer disorganizers who made the event possible.
Should you attend an unconference such as JCrete? You can tell that I am a fan. And I am not the only one. Here you can find other enthusiastic experience reports from JCrete. If you are inspired, check out this list of Java unconferences around the world.
Finally, since I've written so much about AI, perhaps I should point out that I wrote this entire article with my own fingers, em dashes and all. I hope it shows in a good way.
With a Mastodon account (or any account on the fediverse), please visit this link to add a comment.
Thanks to Carl Schwann for the code for loading the comments.
Not on the fediverse yet? Comment below with Talkyard.