Java Records: Making Bad Designs More Convenient

There is an official JEP 359: Records (preview) that proposes to introduce “Records”, i.e. pure data structures into Java. With it, Java continues to follow other contemporary languages into the territory of “multi-paradigm” programming.

This article explores what this change means for object-orientation, Java development and our industry in general.

Old Name Old Feature

First of all “records” are not new nor modern, quite the opposite actually. Records and structures have been around forever in programming, they were present in COBOL in the 60s and then later in widely used languages such as Pascal, C and others.

These languages followed the procedural programming paradigm, which meant that data was modeled as plain data, and then there were procedures where the “business-logic” was implemented by manipulating the data that was either passed as a parameter, available globally or from somewhere else.

Then object-orientation and Smalltalk came along in the 70s and 80s and did away with pure data structures in favor of objects which were thought of as independent agents cooperating and communicating with messages to implement the requested logic. Even in the 90s when Java was created this original idea still persisted strongly enough that Java originally did not introduce pure data structures.

Then our industry virtually exploded in the late 90s, “e-business” was a thing, Java introduced the “Enterprise Edition”, there was a huge demand for Java/JavaEE developers. This is where cracks started to appear on the transition to object-orientation. There were not enough object-oriented developers, so most people (including myself admittedly) came from a procedural background. At the team where I was junior developer nobody explained how object-orientation supposed to work, so we basically continued to produce C code in Java. Instead of data structures we used “Beans”, and instead of procedures we used methods that were awkwardly grouped into random classes, usually called Managers. Most projects at the time operated this way.

In the 20 years since then this basic design did not change much. Layered architectures were already a thing in the 90s, still a thing today. Having a data model (sometimes called “domain model” these days) separately from the logic is still the dominant approach for Java software development by far.

So with this in mind, it seems pretty clear that introducing records is not a step forward at all, rather it is literally a step back towards our procedural past. Were things in the past better, was the procedural approach successful?

The state of our industry

To complete our historical perspective it might be beneficial to look at where we are now in more detail. I will not be able to summarize that more succinctly than this.

Just to stay in the “enterprise” sphere, there is an adversarial relationship between the business and development. Not between people mind you, but between the areas. Development is usually not part of the business at all, they are a “service” the business reluctantly keeps around or even outsources. Development, and IT in general is a “cost center”. That is MBA talk for a thing that just costs money but is not considered useful.

This wasn’t always like this. Even as late as the 90s business people were excited to work with us, to tell us how they operate and asked us to help them come up with new ideas for customers. We worked side by side with them not just developing software, but as a part of business operations. People trusted us because we were there the whole time, saw what was happening with them and how they work. We came up with small enhancements almost weekly. We were also not budgeted separately, but with the business group we were working in. We helped generate money so to speak, instead of just burning it.

So what changed? Well, many things, but most importantly we were not good enough at our jobs. Things took too long to build, they were not working as expected and as codebases grew things got only worse. Then the business started pushing us away and demanded to have some form of contractual security, which resulted in less trust and communication, and so the spiral continued downward.

Despite of these changes we as an industry continued to do things the same way. We still think the same way and we still mostly use the same, essentially procedural approach for software design with some minor cosmetic changes. So even though Java did try to make the step forward towards object-orientation, it seems it now acknowledges that it was largely unsuccessful and it officially reverts back to the more popular procedural approach, that from a historical perspective we already know doesn’t really work well. So why would Java do this?

Motivation and Goals

The main reason given in the proposal to have records is for “modeling data as data“, which would look like this:

record Point(int x, int y) {
}

Let’s look at this approach pragmatically. How readable and maintainable will this code become? Let’s ask some questions:

  • What is this Point for?
  • What does the application do with it?
  • Can I change the int to long?
  • Can I change the Cartesian coordinate system to polar coordinates?
  • Would polar coordinates be more optimal representation?
  • If I would extend this Point to 3D with a z coordinate, what would I have to change?

These are just some of the things we might be interested in when we look at Point. If you use such a construct in an application, you’ll have to search the code for usages, often following through multiple layers and even through other objects where the data is simply copied out to be used somewhere completely unrelated.

So instead of that, how about this:

public final class Point {
   private final int x;
   private final int y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public Line connect(Point other) {
      ...
   }

   public Line throughAt(double angle) {
      ...
   }

   public Circle circle(int radius) {
      ...
   }
}

This is obviously more code, but also much more information. We know what the Point is for, how it is used. We see that the internal representation stays internal, so there is no way the application uses the Point in some other way not defined here. We can also change this internal representation freely, look at whether polar coordinates would be better in light of the logic present here, etc. And we don’t have to look elsewhere to figure all this out.

So which is more “readable”? Which communicates more information? Which is more maintainable? Which is guaranteed to be used correctly? Which can be abused less?

The reality is of course that the proposed “records” are only more readable compared to conventional data-only “objects” in Java, but the proposal completely ignores that the approach itself could be wrong from a historical, maintainability, readability and design perspective.

Functional Programming

There are some arguments suggesting that records are not just read-only “structs” from C, but actually “algebraic data types” from functional programming. And because the words “algebraic” and “functional” sound cool, it must be good.

Functional programming at its core is the idea that software should be a composition of pure mathematical functions. Functions that receive some values (i.e. immutable data) and produce always the same value for the same parameters. Java doesn’t even have a concept of immutable data nor pure functions, nor proper tools for function composition. Even the proposed records are only shallowly immutable, meaning they can refer to other objects that are mutable.

Even ignoring all that, actual real-life designs in Java are clearly not functional, but mostly procedural. It is therefore pretty clear that records will be used to support current designs, regardless what its purpose was supposed to be.

You don’t have to use it

This argument has to be addressed, because this is a frequent response to critique on a new feature: Well, you don’t have to use it if you don’t want to.

This of course completely ignores that none of us work in isolation. We work with colleagues, external partners, consultants or even people we have never even met. Anything the language supports will be used and everybody will have to deal with it.

Also, features of a language suggest usage, an idiomatic way certain solutions are designed and used. To suggest that a language feature can be ignored is therefore unrealistic.

Summary

The coming Java “record” feature is not new at all, it is in fact an official regression into procedural programming. That is the paradigm from the 60s that is partially responsible for the “fall from grace” of software development in the 90s and 2000s. It is a signal that the transition to object-orientation, the paradigm that had the potential to fix the issues of the procedural world, that Java began in the 90s is basically abandoned.

The notion that records are part of the functional paradigm is a red herring. Java is not, and never was functional and real-life Java code is pretty much procedural if anything.

So, while “records” seem to be universally welcomed, people, especially developers committed to the object-oriented paradigm should be extra careful when considering this new feature, or even refrain from using it completely.

11 thoughts on “Java Records: Making Bad Designs More Convenient

  1. Good article once again, but I disagree with one point: “…Java did try to make the step forward towards object-orientation”.

    For example, when procedural programmers picked up Smalltalk, to get access to the private fields of objects from the outside, they would hack around it by creating “mutator” methods, which simply returned or mutate the fields directly. Java basically turned this hack into a language feature with “getters” and “setters”.

    Also, just like C++, in order to avoid the runtime performance overhead of message sending, Java allows direct invocation of methods on objects, further distancing itself away from OOP.

    In my opinion, by the late 1990s, OOP was already out the door. Learning OOP is too high a cost for most people. Top-down/functional design is way easier to approach. Most untrained people, I think, would revert to this approach naturally. Especially now with bootcamps and the promotion of the “everyone can learn to code within a month” initiatives, OOP is doomed.

    Liked by 1 person

    1. I agree with you. I didn’t want to go there in the article, but of course Java itself had Java Beans (i.e. object-workaround structs) almost as soon as the language itself. JavaEE declared itself not object-oriented, but “component-oriented” or “component-based”. Already in the 90’s.

      I always thought that those were not problems with the language though, just with the libraries and frameworks surrounding it.

      I still think that OO is the best way to solve our problems (like maintainability of our code), if we would just get around learning and using it better. Functional programming would also be just as good, if not even better, but that would require even more knowledge, training and even more rigorous design and thinking.

      Liked by 1 person

  2. I really like your articles where you write about history and trends in our industry. I have been writing Java for ~10 years, and enterprise applications just about 5 years, so I haven’t seen much of it myself. I learned about EJB only at university, for instance. Looks like nobody is using them anymore, that seems to me a step in the right direction.

    What do you think we need to do to return to the days when developers worked closely with business and generated money instead of burning it? Object oriented programming can’t be the whole answer because it wasn’t invented yet, right?

    Liked by 1 person

    1. We need to do two things: Get better at what we do, then try to win back the trust of people.

      Doing the second without the first will just lead to even more distrust. This is the problem I’m having with “agile”. Agile basically said let’s start again and build on better communications, let’s have more trust, treat eachother as adults, etc. That’s all well and good, but sooner or later the same problems will crop up as before and people will be even more suspicious about software development then before. We didn’t yet fix anything, so starting over now is premature.

      There are already signs of it failing, if it is taken seriously at all. I.e. the business rejecting to cooperate, or straight up demanding a contractual relationship. Again.

      So first, we need to get better. Object-orientation is a big part of this in my opinion, that’s why I’m writing this blog and go talk at conferences. My goal is to try to show what object-orientation can do for us, because most people didn’t yet see any of it.

      The only realistic alternative is functional programming, which in some respects has even better abstraction capabilities than object-orientation, but unfortunately requires much more knowledge, rigorous design and thinking, mathematics, etc. It is just not realistic to demand from most developers.

      Like

      1. As far as I know, FP wasn’t created for application software, but for working on low-level stuff like compilers and drivers.

        Do you know any good websites where one can truly learn or read about FP from expertise level, similar to yours with OOP?

        Thanks

        Like

        1. Yes, FP on the surface is pretty low-level. It is hard to imagine that just composing functions, like adding two numbers, or manipulating lists can be used to actually implement complex business functions. Fortunately FP has immense abstraction capabilities. There is a perspective where calling two web-services does not differ from adding two numbers for example, and FP has the (mathematical) tools to actually have that abstraction level.

          It is not that different from OO. It is a similarly difficult concept, because people can’t always imagine that simple objects, that only know some tiny part of the whole, can cooperate to produce a complex function, without any central part that “controls” everything, like in procedural programming.

          Unfortunately I have no good sources specifically on business-fp programming, because most sources still concentrate on explaining the very basics, like what functors or monads are. If you just want a taste, I can wholeheartedly recommend http://learnyouahaskell.com/. It’s online and free, although I did buy the printed version also.

          Like

  3. I think that introduction of “records” was motivated mostly but nothing more as the introduction of auto-boxing. – simply to save You some typing. The lack of true pass-struct-by-value, or just returning multiple values from a method forces one to declare some classes just to return two numbers.

    I have to do it frequently and it is usually a pain. So records are fine for this kind of application. Obviously, if You need to return something what is something more, have some specific meaning or something like that declaring class and giving it some functionality is way better.

    Could it be done better? Sure it could. I would be really glad to see an ability to declare a method which returns two or more values. By value, directly on stack, without loading the garbage collector. The JVM should be able to handle it even know, if not the method descriptors.

    What worries me is the default toString() implementation which may result, if record fields are references to objects, in stack overflow or deadlocks. Obviously if one knows that one may override it, but it would be better it if would not be there.

    Like

  4. I think this was a very premature analysis, records are a step forward to remove boilerplate and enforce immutability, your example can be rewritten as:

    ““
    public record Point(int x, int y) {

    public Line connect(Point other) {

    }

    public Line throughAt(double angle) {

    }

    public Circle circle(int radius) {

    }
    }
    “`
    Much better readability with stronger encapsulation.
    What it needs imo is a builder companion to ensure no trivial errors are made, like scrambling the args: `new Point(line.start().y(), line.start().x()) `

    This can help, but didn’t try it yet: https://github.com/Randgalt/record-builder

    With traditional setters the verbosity of `setX(somethingX)` is safer for “enterprise” codebases.

    Like

    1. Thank you for your comment.

      You forget though, that “record” objects always have public getters. So it *can’t* be used as a proper object. I can never be sure that some code somewhere doesn’t use my internal representation. Other than searching for all usages, if I have access to all code where it is used.

      The specification even states that removing boilerplate was *not* the reason to introduce it, but to model data as data. I.e. to be more procedural.

      For removing boilerplate, I would have picked kotlin’s constructor syntax (parameters there can be automatically available as instance variables) and named parameters. Those two solve both the boilerplate thing and builders for the most part.

      Like

      1. OK, I see your point. But you can always pass a record to a normal class constructor and have a bit of both worlds.

        On Mon, Dec 6, 2021 at 5:54 PM The New Java Developer wrote:

        > javadevguy commented: “You forget, that “record” objects always have > public getters. So it *can’t* be used as a proper object. I can never be > sure that some code somewhere doesn’t use my internal representation. Other > than searching for all usages, if I have access to all code ” >

        Like

Leave a reply to Mandate Of Heaven Cancel reply