- 20 minutes read

Two opposite trends dominate the Java world. Frameworks and infrastructure become simpler over time, but the average application becomes more clumsy and convoluted. When I enter a new project, I need more and more time to get to speed. It's not just my age or the demanding business use cases. My impression is the source code is more complex than it needs to be.

When I talk to Java developers, I often learn they don't even notice. They put up with a frustrating development experience, and they don't even know it. To me, writing 400 lines of codes spread over five classes to fulfill the guidelines of the team architect is infuriating if I knew I could get away with 50 lines if only I were allowed to use a more streamlined design. To my astonishment, most developers accept the guidelines without asking. Time and again, complexity is born out of neat simple structuresImage published by Pete Linforth on Pixabay under a Pixaybay license. Time and again, complexity is born out of neat simple structures

I remember joining a project when I asked how to compile and run the application in the local environment, and they told me that was not possible. Next, I asked how to debug the code. That turned out to be a cumbersome process involving starting a unit test via a Gradle command on the command line and remote debugging. In other words, the team used the debugger.

You may argue that's an extreme case, but I doubt that. I've seen two projects that couldn't build on a local machine during the last six years.

Another example. Do you know Hot Code Replacement? If you do, you love it. But most project teams don't even know such a thing exists. More often than not, they've even broken Hot Module Reloading, which is kind of a little brother of Hot Code Replacement.

Complexity is what happens when you stop caring about your code.

Strictly speaking, neither debugging nor Hot Code Replacement have anything to do with code complexity. But there's an indirect connection. These tools break when your pile up complexity without being careful. And nobody seems to care. Quite the contrary, most teams are proud to be able to manage such a complex application.

It doesn't have to be that way. I've spent a couple of years outside the Java universe, and that experience was an eye-opener.

Learning from JavaScript

For a brief period, the virus of "enterprisiness" infected the JavaScript world. In particular, AngularJS started to pick up design patterns. Apparently, the early AngularJS application turned out to be unmaintainable, so using the industry's best practices and design patterns promised a way out. Rob Ashton wrote a famous funny article about this desease. At the time, the AngularJS documentation had this remarkable explanation (and you can still find a similar description here):

An Angular "service" is a singleton object created by a "service factory." These service factories are functions which, in turn, are created by a "service provider." The service providers are constructor functions. When instantiated, they must contain a property called $get, which holds the service factory function.

When I'm talking about "enterprisiness" or "pattern-madness", it's precisely this: piling up pattern upon pattern upon best practice until your business code stops being visible. In theory, there's nothing wrong with the factory pattern. It helps you to keep huge application manageable. Read the Gang of Four book, and you'll buy it immediately.

It the same old story: the difference between theory and practice is that there's no difference, at least in theory.

The practice is that progress in modern programming languages has made most of the patterns obsolete. This even applies to such a conservative language as Java. Just one example: the arrival of the @autowired annotation killed the factory pattern. Spring does a pretty good job as a factory, so developers stopped implementing their own factories.

Rob's insight is even more pointedly. In most cases, using the good old keyword new does the trick just as well. You don't need dependency injection. And by no means do you need a dependency injection that's so convoluted. It may add structure to your application, but it doesn't add clarity. Quite the contrary, your business algorithms vanish behind a wall of boilerplate code.

Luckily, contemporary Angular shed this enterprise stuff. Angular still uses dependency injection, but nowadays it's so simple it doesn't hurt using it.

To be honest, I doubt Angular needs dependency injection. The classical use case for dependency injection is testing. But JavaScript is more flexible than Java. You can mock classes without dependency injection. In fact, there's another JavaScript community that's tremendously successful without caring about dependency injection: Most ReactJS applications get away without using DI. I've even found a remarkable con argument in an article propagating dependency injection in React:

Reasons not to use DI: Might be confusing to other developers

There you have it. Your beloved @autowired annotation that makes your life so much easier is frowned upon in other programming communities.

Tools are not the solution to complexity

Another funny JavaScript story is tool-madness. For a couple of years, the JavaScript community suffered from a frenzy in developing and adopting new tools. Here's an article that nailed it: how it felt to learn JavaScript in 2016. Read it - it's both funny and instructive.

That someone tells you they've used a tool successfully doesn't mean the same tool works in your project, too.
Since then, things have cooled down considerably, because we JavaScript developers have learned a lesson: new tools don't automatically simplify your code. It goes without saying that's also true in the Java universe. No matter if you want to add Gradle, Apache Kafka, NoSQL databases, Microservices, or Event Sourcing to your equation: think twice. Each of them is a valuable tool, but only if you know what you're doing. Adding a framework just because you've heard it's cool inevitably brings you into trouble.

In the case of Gradle, there's a simple benchmark. Can you open your project in Eclipse? If you can't, your Gradle scripts are probably a mess. Most projects I've joined recently have Gradle scripts roughly 1000 lines long, distributed over a dozen files in many directories. Sometimes I wonder why we've abandoned ANT in the first place. In 2020, the average Gradle file is every bit as sophisticated than what we used to call our ANT hell.

What if the business requirements are complex?

It goes without saying that in 2022, our business department demands much more from us than they did twenty years ago. That's a level of complexity you cannot escape.

That's not what this article about. My point is that given the unavoidable complexity of the business, it's high time to think about the way we're organizing our source code. The old best practices, conceived in a different age, have become stale.

But for some reason, we still follow recipes developed in the 90s, when big companies told us we must write software their way. Do you remember EJB? When it came out, Java evangelists told me to split development into seven roles, allowing each developer to focus on their little cogwheel of a vast assembly line. That's a theory that never worked. It was contrived by big companies that wanted to make money with their tools, so they tried to align the development process to their toolchain. Plus, they tried to apply principles that worked in the industry to what's incorrectly called the software industry.

SCRUM reduces complexity

Writing software is not the same as working on an assembly line. Quite the contrary, it's a kind of craftmanship or even art.

That's why agile development is so popular. Instead of splitting the job of a developer into several repetitive sub-tasks, it focuses on the business side and reduces the business requirements to manageable chunks. It's a holistic approach based on the experience that developers love generating business value and making customers happy.

Best practices don't always match your needs

Remains the question of why we still follow the same ideas as two decades ago. We've stopped using XML and SOAP, but for some reason, the development still has an enterprise air. We still believe it pays to use design patterns whenever possible, follow "best practices" conceived in companies ten times our company's size, and organize our source code the way we started roughly in the early 2000s.

  • Developers simplify code by piling up framworks, tools, and patterns until complexity emerges.
  • Design patterns promise simplicity, but in real-world project they don't deliver.
  • Good test coverage is not the solution. It just makes you complex codebase rock-solid and inflexibel.

Many teams feel there's something wrong, so they try to do something about it. Clean code and test-driven development are en vogue.

The problem with clean code and TDD is both approaches make sense. Everybody immediately agrees. It can't be wrong to clean up our code!

The ugly secret is both clean code and TDD distract the developers from the real problems. Clean code focuses on small parts of the source code and writing a unit test also forces you to focus on a particular method. In the long run, you stop looking at your application as a whole.

Clean code and TDD lull you into a false sense of security.

Good test coverage is a poison pill because it increases the cost of change. That's a deliberate decision: bugs are changes, so you want to increase the cost of detrimental modifications. Unfortunately, the cost of beneficial changes also rises. That's particularly true if your start a large-scale refactoring or restructuring.

When your application suffers from quality problems, clean code and TDD are hardly ever the solution. Clean up your application's design and architecture first. Simplify your program until it stops being complex. Once you've achieved that, it's time to invest in decent test coverage. As a bonus, your application has become smaller, so you end up writing fewer tests.

The Clean Code Initiative teaches you how to reduce the complexity of a piece of code - but maybe you can get rid of that code in the first place!

Of course, writing clean code is something you should always do. Clean code is only a bad idea when your start applying it to existing code. More to the point, it's a bad idea if you don't look at your other options first.

Why annotations overcame the XML hell

Many things have improved since the start of the millennium, no doubt about that. We've stopped using XML. Hardly anybody uses model-driven development with UML. Gone are the times when we tried to document every class with UML diagrams. Annotations have replaced both XML-based dependency injection and JPA mappings. Can you imagine (or remember) that a large part of the application used to be XML? Sometimes, the XML files became so complex that they felt like a programming language. Granted, contemporary annotations tend to be even more sophisticated, but there's a huge difference: the annotations are part of the application code. The annotation is in close vicinity of the item it describes.

The principle of locality

That's what I call the principle of locality. If two things are closely related, I want to see them on the same screen. That's why annotations won over XML files so fast: they relieved the programmer from jumping from one folder to a folder far remote. An annotation allows you to grasp the information you need in a single glance.

Let's buy into vertical layers!

Awesome. But if the principle of locality is such a powerful tool to organize source code, what does this say about controllers, services, and entities?

Exactly. Most teams put their controller in one package with many sub-packages. They put their services in another folder with the same sub-packages. The entities live in a third folder, and sometimes there's even more code, such as mappers between entities and DTO, living in just another package with the same sub-packages. The sub-packages usually represent small domains of the business.

In other words, the standard structure scatters the business requirement over numerous packages. In the folder view of your IDE, there's a considerable distance between stuff belonging together. You notice it painfully when you debug a REST call down to the database level. After debugging two or three lines, another file in another folder opens.

  • The locality principle reduces complexity tremendously.
  • Scattering related code over distinct folders get you nowhere, with the possible exception of making good money by working long hours.
  • Replacing horizontal layers by vertical, domain-oriented layers is a great application of the locality principle.

It's better to do it the other way round. Horizontal layering was an idea of the 90s when we didn't have many use cases. So the distance in the folder view was manageable. Plus, we needed a lot of code to implement things we consider trivial today. Just think of JPA. Nowadays, writing an OR mapper and implementing proper transaction management is just adding a few annotations. In the old days, it was hard work. So it made sense to collect this hard work in a single folder. It made looking up algorithms and copying code easier.

Controllers, services, and entities have become rank and slim during the last two decades.

Do we still need horizontal layers?

When I ask developers why they organize code in controllers, services, and so on, they inevitably tell me they do it to know where to look when hunting down a bug. In theory, controllers orchestrate the work while services execute the tasks at hand.

Reality often looks a bit different. Services are an abstraction layer over the database, bridging mismatches between the database structure and your business requirements. Plus, if the database table contains more columns than the client needs, services may condense the data, sending bite-sized chunks of data to the client.

Controllers often do nothing but pass the parameters to the service. And if you're working on a not-so-old codebase, most services exchange data with the database without transforming it.

In other words, we've got two different layers of abstraction, and at least one of them doesn't add business value.

There's an alternative. You could simply use a @RepositoryRestResource.

Maybe it's not that simple, but you see where I'm heading. If you have the privilege to define the database structure, you don't need to simplify the data structure. We invented this stuff when we still believed in a corporate database. This approach, in turn, resulted in a database that served every application, but it did it poorly because it wasn't tailored to the requirements of any particular application. The corporate database was the least common denominator.

In 2022, most of us are in a much more favorable situation. We've abandoned the single corporate database in favor of smaller databases, each tailored to an application. The gap between the application's data model and the database model isn't as frightening as it used to be. We've gained a lot of flexibility and freedom. Let's use it to keep our applications simple!

You'll know when you need services and controllers

Start dividing your application vertically first, horizontally second, and before long, you'll see that most classes of the horizontal layers are trivial. If they don't add business value, drop them.

Corey Cleary has written a nice wrap-up when to use the controller-and-service pattern. As a rule of thumb, you need service when your controller looks fat or when you need to reuse code for another controller. But don't implement controllers and services because you believe them to be an industry standard. They're not. They're ubiquitous in the Java universe but not necessarily among other programming communities.

Named queries

Named queries keep puzzling me. The idea of JPA is to relieve the developer from having to think about the database. Just let Hibernate or Eclipselink figure it out. But that's not what I see in most projects. More often than not, there's an entity class with a long list of named queries. Something like this:

// entity class: @NamedQuery(name="Country.findAll", query="SELECT c FROM Country c") ... // service class: private List findAllCountries() { query = em.createNamedQuery("Country.findAll", Country.class); List results = query.getResultList(); return results; }

Named queries do have their values - in particular for performance. However, prematurely optimizing for performance is a well-known antipattern. It's better to keep it simple and leave the optimization to Hibernate. Your code boils down to a single line:

// service class: private List findAllCountries() { return countryRepository.findAll(); }

Constants considered harmful

Here's another habit everybody loves except me: I'm talking about introducing constants without need.

Let's revisit our named query. I didn't show you the real-life version. Usually, it looks like so:

// global constants class: public static final COUNTRY_FINDER = "Country.findAll"; // entity class: @NamedQuery(name=COUNTRY_FINDER, query="SELECT c FROM Country c") ... // service class: private List findAllCountries() { query = em.createNamedQuery(COUNTRY_FINDER, Country.class); List results = query.getResultList(); return results; }

The problem with introducing a constant is that it violates the principle of locality. We've started with two classes, and we need a third class to define the constant. If there's something wrong with the database query, we have to look it up twice before seeing the implementation. Granted, in this particular case, a global search for COUNTRY_FINDER also shows the named query, so there's little harm.

It's worse if you're really interested in the string value:

public final String HEADER = ""; </pre><p>There's also an HTML entity called <code><header></code>, so this constant is not only a misnomer, it's confusing. Such things happen all the time when you're using constants.</p> <p>More generally speaking, adding a constant is adding another layer of abstraction.</p> <p>You need a good reason to introduce a layer of abstraction. Abstractions can be great tools to organize your code, but they add a distance between you and what's happening, even in the best case. It's an additional concept you have to understand. Poor abstractions confuse your future readers. That's the poor guys trying to fix a production bug at three o'clock in the morning.</p> <h2 class="subheader">Mapping considered harmful <a id="mapping_considered_harmful" onclick="window.location.hash = 'mapping_considered_harmful';return copyLink(id)" title="link to heading" class="header-link" aria-hidden="true" href="/complexity/#mapping_considered_harmful"><img height="1rem" width="1rem" src="./assets/images/link.png"></a></h2> <p>Using constants is a special case of mapping. The strings <code>Country.findAll</code> and <code><title></code> are mapped to the constants <code>COUNTRY_FINDER</code> and <code>HEADER</code>, respectively.</p> <p>That's not unusual. Developers do this all the time. The idea is to replace a bad name with a better name. For example, you can use mapping to fix the ill-chosen column names of your database. In SAP country I've often seen a mapping between <code>GPNR</code> and <code>BPNR</code>. The first name is the German acronym of "business partner number," so it feels natural to rename it if your team speaks English. Requiring your team a term they can't pronounce is demanding too much. Try it yourself: how do you pronounce Geschäftspartnernummer?</p> <p>Most of the time that doesn't work out. You end up memorizing two names instead of one. Don't do that. It's just an additional cognitive load you don't need. If you really can't avoid mapping, keep it trivial.</p> <p>Trivial mappings have another benefit. Using a framework like Mapstruct or Nomin, you can have many of your mappings generated almost without writing a line of code.</p> <p>Looking at the mapping problem from a more general point of view, try to avoid mapping whenever possible. It's a good idea to decouple classes from each other, but in my opinion, mapping isn't the tool of choice.</p> <p>Sometimes you can't avoid it. Mapping DTO to entities and vice versa is one such example. But even this mapping is crap. Contrary to public belief, it doesn't add any business value. It's just a debatable design decision of Hibernate. Hibernate adds a lot of bytecode magic to your entity classes. That's pretty cool because it allows Hibernate to implement the database layer efficiently. At the same time, it makes the entity classes ill-suited for implementing business logic. In particular, you can't simply take a DTO from a REST interface and store it in the database. You have to map it to a Hibernate entity first.</p> <p>I consider this mildly infuriating because I implemented a persistence framework in 2004. In a nutshell, it does the same as Hibernate, just with a smaller scope. We optimized it for the applications of a single company. Ten years later, we considered Hibernate mature enough to migrate our code.</p> <p>One of the key differences was that my persistence framework could store DTOs without further ado. Migrating to Hibernate was painful because we had to add DTOs and mappings we previously didn't need.</p> <h2 class="subheader">The official rationale behind DTOs <a id="the_official_rationale_behind_dtos" onclick="window.location.hash = 'the_official_rationale_behind_dtos';return copyLink(id)" title="link to heading" class="header-link" aria-hidden="true" href="/complexity/#the_official_rationale_behind_dtos"><img height="1rem" width="1rem" src="./assets/images/link.png"></a></h2> <p>For the sake of completeness, let's have a look at what Baeldung - who's usually a reliable source in all things Spring - says:</p> <p><quote><a target="#" href="https://www.baeldung.com/java-dto-pattern">DTOs or Data Transfer Objects are objects that carry data between processes to reduce the number of methods calls.</a></quote></p> <p>Does this reflect the way you're using DTOs? I doubt it. The DTO pattern sounds like a powerful tool, especially if it is not limited to trivial mappings.</p> <p>However, there's a superior design pattern. Check out the <a target="#" href="https://medium.com/mobilepeople/backend-for-frontend-pattern-why-you-need-to-know-it-46f94ce420b0">backend-for-frontend pattern</a>. It hides the complexity of the backend to the frontend. In most cases, that's more useful than hiding the complexity of the backend to the backend, as Baeldung's definition of the DTO pattern indicates.</p> <h2 class="subheader">Wrapping it up <a id="wrapping_it_up" onclick="window.location.hash = 'wrapping_it_up';return copyLink(id)" title="link to heading" class="header-link" aria-hidden="true" href="/complexity/#wrapping_it_up"><img height="1rem" width="1rem" src="./assets/images/link.png"></a></h2> <p>That's it for today. I've started the article by observing that JavaScript developers organize their source code differently from Java developers without sacrificing maintainability or readability. But they get away with less code. That made me think. How come I always feel I'm wasting my time writing boilerplate code when joining a Java team?</p> <p>My key message is to encourage you to replace your controller, service, and entity layers with vertical layers. Organizing your code around the business use case almost always reduces complexity dramatically.</p> <p>I also encourage you to resist the urge to make everything a constant. The mantra "make it a constant if used twice" stems from a time before the invention of global search. At the time, we only could look at a single file at a time, so constants were extremely useful. Change one file to modify the behavior of many other files. Nowadays, declaring constants is much more prevalent than in the old days, but it's become mostly pointless. They say, "strings are easy." Just ignore Sonarqube and IntelliJ nudging you to declare a constant when it makes reading the source code more cumbersome.</p> <p>However, my pet key message is the principle of locality. Code that belongs together must not be scattered across classes, packages, or even projects. Allow neighbors to be neighbors. Every other piece of advice of mine follows this principle. Give it a try, and you'll get rid of complexity!</p>

Comments