Your team says it’s working on new features. But they’re not getting anywhere. Small changes take longer than expected, bugs keep appearing, and when you ask, you get explanations that sound plausible at first glance. But your gut tells you: something isn’t right.
This article is for you if you don’t come from a development background and can’t tell the difference between architectural problems and normal challenges. I’ll show you how to recognize whether your software development is on the right track. And what you can do when the signs point to trouble.
Your development team is using AI coding agents. Subscriptions are paid, some developers use them actively, others are still stuck at chat-completion. And the productivity gain the tool vendors promised still hasn’t materialized.
It’s not the model. It’s not the tool: The missing lever is context, and most teams aren’t providing it systematically.
This isn’t a new problem. AI agents just make it more visible. What agents can already do today — and where the limits lie — I’ve described in KI in der Softwareentwicklung
.
You gave your development team AI tools. Copilot licenses for everyone, access to coding agents, the whole package. A few weeks later, you notice: the results you expected aren’t materializing. Features aren’t shipping faster. Quality hasn’t improved. And when you ask, you get shrugs.
This isn’t an isolated case. In client projects and workshop requests I receive, I see the same pattern: the tools are there, but the impact isn’t. The cause is almost always the same — it’s not the tool that’s missing, it’s the enablement.
Your team has Cursor subscriptions. Or Copilot. Or Claude Code. The invoices are paid, the tools are installed, and now AI is supposed to boost productivity. Except it doesn’t. A few developers experiment, most carry on as before, and nobody feels like anything has changed.
The problem isn’t the tool. The problem is that a subscription isn’t enablement.
At JavaLand 2026, during my talk “Context Is Everything,” I asked about 50 developers where they stood on the AI adoption scale — using Steve Yegge’s 8-stage model
, which ranges from “Zero or Near-Zero AI” to “Building your own orchestrator.” At level 1, all hands went up. By level 3, it got lonely: only four or five hands. From level 6 onward — not a single one.
Your development team says: “We should start using AI tools.” The question isn’t whether they’re right — the question is: what does that actually mean? What does it cost if you get it wrong, and what does it cost if you do nothing?
This article is written for both sides of the desk. For the CTO who needs to explain to the board why AI tools in the dev team make sense — and for the CEO who wants to understand what the team means when they talk about “coding agents.”
When developing complex software systems, teams often go through a documentation cycle that follows an almost predictable pattern. The initial euphoria is characterized by ambitious goals: comprehensive documentation should emerge that illuminates all aspects of the system and makes every decision traceable. With fresh enthusiasm, tools are evaluated, templates created, and initial documentation written.
But as the project progresses, neglect creeps in. Development pressure increases, features take priority, and documentation becomes a “nice-to-have” that will be addressed “later.” This phase marks the critical turning point where the foundation is laid for the long-term success or failure of the documentation strategy. This article shows how to prevent a very time-consuming failure.
I recently attended Team ‘24, Atlassian’s flagship conference, where I had the chance to meet with Matt Schvimmer, Senior Vice President and Head of Product for Atlassian’s Agile and DevOps portfolio. We discussed how Atlassian tools support modern software development.
Atlassian tools like Jira, Bitbucket, Compass, and the newly announced Focus play a critical role for teams operating in complex environments. Schvimmer provided insights into the evolution of Atlassian’s tools and their future roadmap. He also addressed challenges such as synchronizing documentation with code, managing microservice complexity, and tackling tool over-configuration or cloud-related concerns.
As a young developer, I was naive and ambitious. I searched for the perfect software architecture, an approach that I could impose on any problem, regardless of industry, scaling requirements, security measures, or users. If you could solve this one problem abstractly, then surely this architecture could be applied everywhere. Right?
This article explains why the pursuit of the perfect software architecture is an illusion. Current trends and fads often dictate universal solutions that, however, lack the necessary flexibility to survive in the market. This is especially relevant for technical leaders and decision-makers in SMBs. But how can you design an architecture that is flexible enough to adapt to changing requirements?
What if…? I must have read this sentence hundreds of times in the last few days regarding the CrowdStrike outage. Everywhere, people are looking for someone to blame. And that is wrong!
We are witnessing the classic blame game. It is more important to find someone to blame than to solve the problem. This only fosters fear.
An employee of Tom Watson, the founder of IBM, once made a mistake that cost the company ten million dollars. When the man was called into Watson’s office, he was sure he would be fired. To his surprise, his boss said: “Fire you? Not after I just invested ten million dollars in your education.” ~ The Winners Laws by Bodo Schäfer (translated from German edition)
HashMap? How can HashMap be a topic for the newsletter? Hold on!
The topic came to mind because I was in an interview for a client yesterday and spent quite a long time discussing the HashMap. The experience was a bit of déjà-vu. 2-3 years ago, I was heavily involved in recruiting. During that time, I conducted over 150 technical interviews. And from that period, I learned a lot about our industry. First and foremost, we lack seniority and fail to sufficiently develop young developers.
There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors. – Leon Bambrick
Or another version:
There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery – Mathias Verraes
And there are many more:
There are so many variations on the “there are only two hard problems in computer programming…” joke that I’m starting to suspect that programming isn’t actually very easy. – Nat Pryce
You probably know this from your everyday life. You have a question about your mobile contract. So, you call the hotline. A computer answers. You press 1… then 3… then refuse to have the call recorded for training purposes, and finally, you end up with some employee in a call center. You explain your problem. The employee has a script in front of them and goes through it with you from top to bottom. If they’re good, you don’t even notice it.
In recent years, numerous medium-sized companies have tried the much-touted microservices and found that they are less lightweight than their name suggests – Microservices in SMEs are often overengineering
. Now, they need to be deconstructed and transitioned to a more pragmatic architecture. We explain how this can be achieved.
Don’t worry: The deconstruction and transformation of the architecture is not only labor-intensive but also an opportunity. In software development, there is no one-size-fits-all solution; the appropriate architecture for the software is chosen based on the software’s requirements. This involves gathering the requirements of all stakeholders, which is often not adequately done in many projects. An architectural transformation allows for this to be rectified and reviewed.
Agile is not something that can be bought. The manifesto describes how to develop software in an agile (as an adjective!) manner. It does not describe a product.
Ten years ago, most companies were still dealing with manually managed infrastructures. Today, many medium-sized businesses still find themselves in the same situation. But that comes at a cost - and I’m feeling it right now.
It’s astonishing how often we chase ghosts simply because our environments differ. This ranges from obvious things like available CPU or RAM to subtler issues like different kernel versions or configuration disparities in applications. It gets especially tricky with external application servers, where configuration files are spread throughout the system and can easily drift apart.
In one of my client projects, we reflect on our working methods.
Like most mid-sized projects out there, Scrum is used here too. But we have a few problems: - Sprint goals are never met - Not a sprint goes by without a new task coming in from the side - Estimations are regularly far from reality - Collaboration with other external parties is partially not integrated - … I can certainly think of more examples
For more than a decade, hardly a conference goes by without the topic of microservices. Industry magazines publish numerous articles on the subject, and external consulting firms sell workshops, actively advise on switching to a microservice architecture, and tout its benefits. For many small and medium-sized enterprises (SME), this sounds attractive—what helps the big players so much can’t hurt the smaller ones, right? This is a dangerous misconception.
Overengineering — the use of unnecessarily complex software — costs SMEs not just a lot of money and time, it also deprives them of their most significant competitive edge against large corporations.
You probably know the meme. Even after decades, I still find it funny 😁
But what I haven’t managed to do in all these years is to seriously learn how to use vim. Sure, I press i to enter insert mode and then I can type. And I’ve also learned dd to delete a line. And yes, of course, I was one of the three million viewers who were helped by Stack Overflow
to close vim 😉
In the beginning, IT teams often implement new features quickly, but over time, development tends to slow down. Accidental Complexity is a frequent culprit – this article explains its origins and how it can be mitigated.
It’s no secret that systems grow more complex over time, leading to longer development cycles. This is inevitable when dealing with inherently complex problems, known as Essential Complexity. However, in many cases, the implementation of a feature becomes more complex than necessary. Two months for a feature that seemed straightforward? That’s a classic case of Accidental Complexity. It’s frustrating, costly, and avoidable, as we’ll show with some practical examples.
You might have heard that null was referred to by its creator, Tony Hoare, as the “billion-dollar mistake.” In 2009, he publicly apologized at QCon for taking the easy path in 1965 by inventing null:
“I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object-oriented language (ALGOL W
). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.”
[…] According to Survivorship Bias, the probabilities of success are systematically overestimated because successful individuals or states are more visible than unsuccessful ones. ~ Wikipedia
This cognitive bias is interesting because it affects us in development as well. For 15 years, I have been working mainly in small and medium-sized enterprises (SMEs), which are companies with up to 250 employees. Clearly, these are not large corporations.
Currently, I am writing a new article for Golem.de.
In this context, I have been reflecting on the similarities in software development in small and medium-sized enterprises (SMEs).
The German Federal Statistical Office defines SMEs as companies with 10 to 250 employees and a turnover of less than 50 million euros.
At first glance, this range seems very wide. Intuitively, one would expect the challenges and solutions to be very different as a result. And to some extent, this is true. However, this does not align with my experience over the last 15 years, during which I have worked exclusively in SMEs. On closer inspection, it turns out that the number of developers is not that different after all.
This year, I’m a bit behind with the newsletters. For one, I’m deeply involved in client projects – and the client always comes first. Additionally, since the beginning of the year, I’ve been battling pneumonia. It only started to improve this week. The symptoms were mild - after all, I continued to work - but I noticed a lack of energy for tasks beyond my duties.
To get back into the swing of things: Today, I have three short topics for you.
As written on Monday
: Small and medium-sized enterprises (SMEs) have no problem with performance.
However, that’s not entirely true. Over the past few years, I’ve worked on many products that were slow. Improving performance would have been positive for the business. In certain areas, like the gaming industry, it was inevitable to be performant. Retention is one of the most important metrics in the industry. But that’s the exception, not the rule.
My positioning as a performance expert has failed.
But not because I can’t deliver. There’s simply no demand from my customers.
In hindsight, it makes sense. But let me explain:
Ever since I’ve been developing software, I’ve been working in small and medium-sized enterprises, known as SMEs. These companies have between 5 and 50 developers, occasionally a few more.
Especially in software development, many people work who are particularly good at abstraction and objective consideration of problems and questions.
It’s in the nature of the thing. It’s our job to thoroughly understand a problem in detail. And the solutions always require weighing the pros and cons.
But there are always problems and habits that have become entrenched in our industry that, objectively viewed, make no sense.
Perhaps you’ve already experienced that not every person has exactly one first and last name. And maybe you’ve had a support case where names change. After all, there are people who get married 😉
If you belong to the first category: Cool! Either you’re not live yet and have the freedom to develop trunk-based, or your organization is so advanced with automation and the DevOps mindset that you can develop trunk-based too.
Everything’s cool! Nothing to see here. See you in the next newsletter!
For me, it was becoming self-employed in 2023. What an adventure!
The year is now over, and I enjoyed the holidays to unwind and spend time with my family.
Believe me, in the first year of self-employment, it’s leisure time that suffers the most 😉
So much happened last year. There were so many highlights. And there were so many interesting topics. Today, I want to share three of my many highlights from last year with you. Buckle up. Here we go!
He provides self-help for people who hate self-help books. In this paradox, he refers to life decisions. For example, many people find it hard to enter into a love relationship via Tinder. The options are so vast that with each date, you think you might have something better.
They had gotten comfortable. The software has been in development for more than 10 years. And over time, substantial technical debt had accumulated. It was a creeping process.
I heard this proverb early on, probably for the first time during my school days. It’s a wisdom that we have carried forward for a long time.
Perhaps you associate this proverb more with areas like marketing, statistics, politics, or you think, like me, of the wisdoms of our teachers back then.
Something I’ve realized repeatedly in recent years is: It’s also relevant for us in software development.
The 343 comments on my article clearly show how heated this topic can be 😉 Of course, the rational view on it is different from what I presented in that article. It ALWAYS depends on the context. AppContinuum
is an excellent paper offering a reflective view on the subject.
What happens when we write a UserServiceTest class? We have - without realizing it - established a semantic coupling between the UserService class and the UserServiceTest.
And it was going so well. The meticulous preparation of the past weeks had paid off. Everything was going according to plan. The necessary changes to the VMs went as expected. The major migration was successfully completed after one hour of runtime. The new content files were successfully deployed with the new system. The new CD pipeline ran for the first time on production – and it was successful.
After almost two years without a release, this Saturday marks the big day. One of my clients hasn’t released for two years - incredible 🤯
Why? There are many reasons.
The team got entangled in complexity. Significant (and necessary) changes were made.
But these led to numerous side effects. And the goal was to do everything “right.” So, development continued. With one topic completed, five new ones emerged.
My fingers were tingling at the start of Thorben Janssen’s
session “The Big Java Persistence Q&A”.
The first question was roughly:
“In the previous talk (something with JPA), it was said that one should annotate every test with @Transactional. This will automatically roll back the data after the test. We’ve had a discussion and can’t agree. What’s your recommendation?”
Thorben’s answer made me nervous. He countered with a question of his own, also paraphrased:
Nervousness sliced through the room. I saw it in the eyes of all attendees.
It was supposed to be just a routine meeting.
Once a week, the development team meets with the admins to discuss current topics.
This meeting has been held for years.
Same time, same people, (almost) the same topics.
But something was different this time.
A new colleague participated for the first time. An external.
We brought him in to work on “DevOps”. Mainly automation. CI/CD, provisioning, tooling, compile-time optimizations. Everything that improves the developer experience.
For a treasure hunt for a 5-year-old, I want to write a text. The text should be written by a pirate, describing a riddle where the pirate’s treasure is hidden. It should be hidden in a place where creepy trees are. Can you write the text for me and make it rhyme?
“After importing, we unpack the file and read the meta-information.”
“And where do we write it?”
“This information is stored in different databases. The path information is distributed across all service databases so that they know how to access the files. Other data is stored in the system database and in each tenant database.”
In criminology, the broken windows theory states that visible signs of crime, anti-social behavior, and civil disorder […] encourage further crime and disorder […].
Dave Thomas and Andy Hunt have applied this concept to software development.
Whenever there’s poor design, an unclear class, inadequately tested code, or unused assets in the repository, it becomes more likely that more “broken windows” will follow.
You probably know this feeling. You want to make a Breaking-Change in your system. Maybe you want to change an API. Or you’d like to switch up your environments. Or you want to shut down a service because it’s no longer needed.
Whenever I face such a decision, I wonder about its consequences. Will it affect other stakeholders? The answer is often, yes.
This is the case now. I want to conduct deployments more frequently in an environment. It’s used to test the upcoming release. And, to keep the feedback loop short, I always want the current development status on the platform.
Today, I gave a presentation titled “Pragmatic Programming with Kotlin” at KKON.
It was once again a great pleasure. And because the RabbitMQ Summit
is coming up this Friday, followed by the W-JAX
in two weeks, I really didn’t feel like creating more slides.
So, I opted for live coding.
That way, I’m certainly faster in preparation (tm).
But aside from my laziness, there was another reason why I chose to do this.
Currently, I am practicing calligraphy. My handwriting has always been purely “functional.” Every day, I write out all the letters of the alphabet, focusing on the quality of each stroke.
Five years ago, I practiced touch-typing. Every day for 10 minutes on https://www.keybr.com/
.
Seven years ago, I banished my mouse within the IDE. I had to learn how to operate everything using the keyboard alone.
And ten years ago, I focused on the Shell. I performed all operations on my system there.
Java 21 has finally reached General Availability (GA) status since Tuesday 🎉
For months, I’ve taken every opportunity to talk about how virtual threads are the highlight of this release for me. For instance, in this
or this
old newsletter.
And that hasn’t changed. I believe virtual threads are one of the most significant improvements in years. Whether everything turns out as expected remains to be seen.
But of course, there are also other features that shouldn’t go unmentioned.
If you’re already working with the Spring Framework, you’ve undoubtedly recognized its power and versatility. One of the valuable - yet often overlooked - features of Spring is its event-handling system. In this article, we’ll delve deeper into how you can configure events in the Spring Framework using @EventListener. Moreover, we’ll illustrate how these events can be paired with transactions and asynchronicity.
Events are an integral component of the Spring Framework. They allow for the creation of loosely coupled applications. Spring Boot employs events to communicate the application’s status. An example of this is the ApplicationStartedEvent. However, you can also publish your own events. And this is possible out-of-the-box, without boilerplate and with minimal effort.
Toni, an external DevOps Engineer (shoutout to you 😉), was new to the team.
On his website he writes, “As an expert in Developer Experience, my goal is to create an ecosystem for high-performance & innovatively-acting development teams.”
And he lives it. There is also more than enough to do. And we want to tackle it!
However, I have a little quirk when it comes to buying anything that’s not perishable. I want to avoid making a bad purchase. I’d rather spend more money once than have to buy a new one in two years.
Here, I have to be careful not to fall down a rabbit hole. I can spend hours reading reviews.
A function takes an input x and transforms it into an output. If f(x) = 2 * x, then the result of this function for the input of any x is always the same.
There are no side effects. We call this behavior stateless.
But what does this have to do with the question from the beginning? An application goes beyond this. (Almost) every application must work with state. As soon as we have I/O operations in our system, we inherently have state. And often a method call, with the same input, can yield different results at different times. And this is also unavoidable and is in the nature of the task we want to solve.
One of my clients achieved something extraordinary. They have successfully operated a product for 10 years. Highly profitable.
But the project was not started by trained developers. It began as a side product. The initial developers were career changers. Highly interested, motivated, engaged, and experts in their domain.
And they accomplished something that fails in 9 out of 10 cases. Their product is actively used. Customers happily pay for it. It solves a real problem. In a niche that hardly any other company can occupy.
Most software developers are familiar with Hexagonal Architecture
and its extension, the Onion Architecture
. The idea (simplified): One decouples their business code from infrastructure code using adapters.
When was the last time you lost track of time? Just keep going. Without it being hard. With full concentration. You shut out everything around you. You forget to eat. You delay going to the toilet.
You are new to a project. Maybe you have just changed jobs. Or an opportunity has arisen internally - whether you wanted it or not.
Your first day. You really don’t know anything about the project. So almost nothing.
You know what it’s about. You may have even tried the product yourself.
But you don’t know what the architecture looks like. You don’t know how many services the product operates. What they run on. How they’re interconnected. Where the data is persisted. Is it relational at all?
I once developed a backend with just two engineers. It was the backend for Sunrise Village
.
I joined the team already in the pre-production stage, together with my then colleague. For over three years, we were the backend team.
The other teams were larger. We had up to 8 frontend developers, a similar number of artists, two game designers, product owners, product managers, and 2 test automators.
Even the fact that there were only two of us was actually too many. We kept it that way for risk minimization - in case one of us was unavailable.
Class comments, architecture documentation, method comments, API documentation, inline comments, feature documentation, wireframes, entity-relationship diagrams, use-case diagrams, process documentation, end-user documentation…
There are so many things one can document. But what do I really need?
Documentation doesn’t write itself. Someone has to take the time. And for it to truly add value, it needs to be well-written, as complete as possible and focused. Not everyone can do that.
In large corporations - with thousands of employees - a lot is documented. There are software architects who have the time and education to write good documentation. There are so many developers that enough time is allocated to document the code. And technical writers produce the highest quality end-user documentation.
An error in accounting can be costly. Especially in large companies, where there are many transactions, it is easy to overlook one.
And when that happens, I might not even notice it. Perhaps the company is doing much better than the books indicate? Or maybe it’s actually unprofitable?
Merchants in the 13th century recognized the problem. A system had to be found that would make errors less likely. They invented double-entry bookkeeping.
On the JVM, one of the largest changes in years is looming over us.
And it will massively influence the way we work.
Asynchrony is difficult. There’s so much to consider:
How do I configure my thread pool? Does the code path have more wait-time, or does it take a long time to calculate? And how large should the queue be in front of the thread pool? Are spikes in the code path to be expected?
Many companies utilize modern technologies. Kubernetes, RabbitMQ, AWS, Docker, ArgoCD… and so on. The selection is vast. The CNCF Landscape
has arrived in most businesses.
But what surprises me: The software is complicated.
So complicated that it can’t just be launched locally on a whim.
I did not expect that.
Docker is deliberately included in the list. Because most of the companies I spoke to use Docker. Many of them in production.
What distinguishes a software developer from a carpenter?
The carpenter produces furniture. The developer creates software.
Imagine you go to a carpenter. You enter the workshop, see various materials, tools, finished and unfinished pieces of furniture. The foreman approaches you.
He asks you about your preferences. You want a table. With an inset plexiglass top. For outdoor use.
Matching your preferences and with his expertise, he selects the right wood. For outdoor use, it needs to be a weather-resistant material. It shouldn’t warp significantly to prevent the inset top from breaking.
Have you ever written your own password encoder? I haven’t exactly - but I had to use a homemade one. In a central User-Management-Service, the customer data of all users came together. And in this, the passwords were stored. Thus, when registering a new user, the same encoder had to be used.
In practice - fortunately - it did not blow up in our faces. But it was highly critical. The encoding was not strong. The Salt was still based on MD5. Even at that time, it was no longer a strong hashing algorithm. The trivial solution to make it “safer”: Simply apply it twice. No one will think of that 🤦♂️
Laziness prevails. Always. Countless times I’ve tried to adopt beneficial routines.
I joined a gym - after a few months, I barely went anymore.
I baked my own bread - after (admittedly) two years, I no longer did.
I got up at 5 am to meditate and exercise - I couldn’t maintain this for more than 4 weeks.
As good as these routines would have been, had I kept them up for years - they come with high investments. Carrying them out takes a lot of energy. To muster up the will, pack your things, hop on a bike, and head to the gym. That requires energy.
El Niño
is back. The surface temperature of the Pacific Ocean periodically fluctuates. This seemingly inconspicuous change triggers a cascade of weather changes that extend their influence to the most remote corners of the globe. Floods in South America, droughts in Australia, and even snowstorms in North America - all caused by a few degrees of temperature difference in the Pacific. This phenomenon occurs every 2 to 7 years. This year, it’s that time again.
Over the past years I attended hundreds of interviews. Many candidates proudly told tales on how they develop their projects with a microservice architecture. Often (I don’t want to say “always”, but from my memory I think it actually is “always”) it does not require many questions to see that they used a rocket launcher to kill a mouse. Microservices are hard. Everyone who experienced the pain of operating such an architecture can relate to it. The complexity kills you at one point or the other. You already had to do multiple refactorings of your architecture - because your domains didn’t work out. I wonder - why is this architecture so appealing to developers? And then I remember why I found them appealing 10 years ago.
The javax.validation package is widely used in our industry. And I don’t like it. I believe using bean validation is
an anti-pattern. It hides business relevant constraints, it leaves the choice when a validation happens to other
framework code, and I even saw cases where developers expected that the validation “just had to take place”, but it
never happened. Of course, there was also no test for it. And speaking about tests - testing these business relevant
constraints is painful as well.
In my last post
I discussed the downsides of using numerical types
for the primary key of an entity. We should avoid these issues all together by using UUID instead and in this post I
will discuss the up- and downsides of this approach.
Using a UUID as the primary ID is simple:
@Entity
class Flat(
@Id
val id: UUID = UUID.randomUUID()
)
Similar to numerical ids we can generate it on the database as well. But for UUID this is not necessary. The reason we
did this in the first place is that we needed to make sure that an ID is not used twice. A collision of UUID is very
unlikely to occur.
In many (JPA) applications numerical ids are chosen for the surrogate key
of the entities. But how do we make sure that they are not used twice? In a scenario where our application needs to scale horizontally we need a solution for that. Most developers come to the conclusion that the database should take care of that. But this is a fragile solution and in this article I want to discuss it.