DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • How AI Is Transforming Software Engineering and How Developers Can Take Advantage
  • AI Augmented Software Engineering: All You Need to Know
  • Responsible AI Is an Engineering Problem, not a Policy Document
  • Why Human-in-the-Loop Still Matters in AI-Assisted Coding

Trending

  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  • S3 Vectors: How to Build a RAG Without a Vector Database
  • Introduction to Tactical DDD With Java: Steps to Build Semantic Code
  • Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. AI in Software Engineering: 3 Critical Mistakes to Avoid (and What to Do Instead)

AI in Software Engineering: 3 Critical Mistakes to Avoid (and What to Do Instead)

Learn the mistakes developers make and how to avoid them. Use AI to accelerate development without sacrificing code quality, architecture, and long-term maintainability.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
May. 06, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
1.3K Views

Join the DZone community and get the full member experience.

Join For Free

The term tool comes from the Old English tōl, meaning an instrument to achieve a purpose. Historically, every major shift in engineering — from the steam engine of the Industrial Revolution to the rise of modern computing — has followed the same pattern: tools amplify human capability, but they do not replace judgment. Artificial intelligence is no exception. It promises acceleration, abstraction, and productivity, yet it also carries a familiar risk: when misunderstood or misapplied, it scales mistakes just as efficiently as it scales success.

In software engineering, this duality becomes even more evident. AI can generate code, suggest architectures, and even simulate reasoning, but it operates on probabilistic patterns derived from existing codebases — many of which reflect average, not excellent, engineering practices. The result is subtle: systems that work, but are fragile, inconsistent, or difficult to evolve. This is not a failure of AI itself, but of how we, as engineers, frame and guide its use. Much like early adoption of frameworks or cloud computing, the real challenge is not the technology — it is the discipline required to use it well.

In this article, we explore that tension through practical experience. Drawing from a Hackathon held in Coimbra during Shift APPens, where multiple teams built Java applications integrating AI with Jakarta EE, we will examine the key lessons learned. More importantly, we will analyze the most common mistakes observed during the event — patterns that are already emerging in real-world projects — and how to avoid them when applying AI in your own systems.

The Challenge: Building Real Systems With Java, Jakarta EE, and AI

Every engineering exercise begins with a constraint. In this case, the challenge was deliberately simple to state yet non-trivial to execute: build real applications in Java, powered by Jakarta EE, and integrate AI into the system itself. The focus was on designing and implementing meaningful integrations—connecting AI capabilities into applications using APIs, persistence layers, and well-established architectural patterns. How the code would be written, however, was intentionally left open.

Interestingly, most participants naturally gravitated toward using AI assistants during development. Not because it was required, but because it has already become part of their workflow. Through prompts, they generated code, refined implementations, and explored alternatives—what is increasingly referred to as “vibe coding,” in which natural language serves as the primary interface for software development. This emergent behavior was revealing: AI was not just part of the system being built; it became part of the engineering process itself.

The results were, at first glance, impressive. During the Hackathon at Shift APPens, eight projects were delivered within a short time frame. Participants explored a wide range of Jakarta EE specifications, including Jakarta REST for APIs, CDI for dependency injection, Jakarta Data, and Jakarta NoSQL for persistence, among others. The audience was diverse — ranging from high school students to experienced professionals — and yet a common realization emerged: Java, when combined with modern standards, is far more accessible and productive than its reputation suggests.

At the same time, this freedom exposed important patterns. While AI accelerated development, it also introduced inconsistencies, design trade-offs, and, at times, outdated approaches. Some participants recognized gaps in their previous practices, while others discovered modern Java paradigms for the first time. This combination — speed, autonomy, and friction — created the ideal environment for learning. In the next section, we will explore the three most common mistakes observed during this process and, more importantly, how to avoid them when applying AI in your own software projects.

Mistake #1: Blind Trust in AI-Generated Code

One of the most recurring patterns during the Hackathon was surprisingly simple — and historically familiar: trusting the tool without questioning the output. This is not new. If you look back at earlier waves of abstraction — from code generators in the early 2000s to ORM frameworks during the rise of enterprise Java — engineers often assumed that generated code was “good enough.” AI simply accelerates this tendency. Participants frequently accepted the first version of the code produced by AI assistants without applying constraints, reviewing trade-offs, or refining the prompt.

The problem is structural. AI models are trained on vast corpora of publicly available code, which means they tend to reproduce the statistical average of what exists. And the average, as any experienced engineer knows, is rarely aligned with high-quality software design. The result? Code that works — but carries hidden costs: outdated patterns, unnecessary complexity, security vulnerabilities, and designs that are difficult to evolve. This explains a growing concern across the industry: organizations adopting AI-assisted development are beginning to report increased maintenance overhead, not because AI is flawed, but because it is used without sufficient engineering judgment.

A concrete example from the Hackathon illustrates this clearly. When prompted to implement basic repository operations — save, find all, and search by name — the AI assistant generated a traditional Jakarta Persistence-based implementation. Technically correct, but verbose and arguably outdated for the given scenario:

Java
 
@ApplicationScoped
public class RecipeRepository {

    public List<Recipe> findAll() {
        EntityManager em = createEntityManager();
        try {
            return em.createQuery("SELECT r FROM Recipe r ORDER BY r.recipeName", Recipe.class).getResultList();
        } finally {
            em.close();
        }
    }

    public Recipe findById(Long id) {
        EntityManager em = createEntityManager();
        try {
            return em.find(Recipe.class, id);
        } finally {
            em.close();
        }
    }

    public List<Recipe> findByName(String term) {
        if (term == null || term.isBlank()) return findAll();
        String t = "%" + term.trim().toLowerCase() + "%";
        EntityManager em = createEntityManager();
        try {
            return em.createQuery(
                "SELECT r FROM Recipe r WHERE LOWER(COALESCE(r.recipeName,'')) LIKE :t ORDER BY r.recipeName",
                Recipe.class
            )
            .setParameter("t", t)
            .getResultList();
        } finally {
            em.close();
        }
    }

    private EntityManager createEntityManager() {
        EntityManagerFactory emf = JpaUtil.getEntityManagerFactory();
        return emf.createEntityManager();
    }
}


There is nothing inherently “wrong” here. But context matters. For a Hackathon focused on productivity and modern enterprise Java, this approach introduces boilerplate code, manual resource management, and unnecessary complexity. The same behavior can be expressed more declaratively using Jakarta Data:

Java
 
@Repository
public interface RecipeRepository extends BasicRepository<Recipe, Long> {

    List<Recipe> findByName(String name);

}


The difference is not just syntactic — it reflects a shift in abstraction level. One approach exposes infrastructure concerns; the other aligns more closely with domain intent. The AI did not choose incorrectly — it chose commonly. And that is precisely the point.

The lesson is clear: AI does not replace engineering judgment — it amplifies it. If you do not constrain the problem, define the architecture, and request better alternatives, you will get code that mirrors the average of the ecosystem. This is why it is critical to:

  • Review and challenge generated code
  • Refine prompts with architectural intent
  • Explicitly request modern approaches and best practices

Without this discipline, you are not accelerating development — you are accelerating technical debt.

In the next section, we will explore the second mistake observed during the Hackathon: what happens when AI is used without sufficient context, and why this leads to inconsistent and fragile systems.

Mistake #2: Missing Context — When Prompts Ignore Architecture

If there is one idea that has remained constant throughout the history of software engineering, it is this: every decision carries a trade-off. This is not a new insight — it is a foundational principle, well articulated in works like Fundamentals of Software Architecture: A Modern Engineering Approach. Technologies evolve, tools improve, but the nature of design does not change. Before choosing how to implement something, we must understand why we are building it that way. AI, however, operates in the opposite direction by default — it starts with how, generating code immediately unless guided otherwise.

During the Hackathon, this mismatch became evident. Many participants relied on prompts to generate implementations without first establishing architectural intent. The result was not broken systems, but inconsistent ones. Different parts of the same application followed different styles, abstractions, and patterns — depending on how each prompt was phrased. In some cases, one class would follow a clean, declarative approach, while another would revert to low-level, imperative code. The issue was not the AI itself, but the absence of context: no shared guidelines, no consistent direction, no enforced design boundaries.

In software architecture, context is everything. It defines:

  • the level of abstraction you choose
  • the conventions your team follows
  • the trade-offs you are willing to accept

Without this, even well-written code becomes difficult to maintain. Early in a project, this inconsistency might seem harmless — even invisible. But over time, it accumulates into what can only be described as architectural drift. What begins as productivity turns into fragmentation, and eventually someone must step in to “clean up” what uncontrolled AI-generated content has produced.

This is why guiding the prompt is not optional — it is essential. You are not just asking for code; you are defining constraints, style, and intent. If you do not explicitly state these, the AI will default to whatever is statistically common, not what is appropriate for your system. The goal is not to avoid AI, but to use it with direction, ensuring that generated code aligns with your architecture and remains coherent across the codebase.

In the next section, we will explore the third mistake observed during the Hackathon: how AI can accelerate a classic problem in software engineering — overengineering — and why moving fast can sometimes push you further away from a good design.

Mistake #3: Overengineering — Faster Than Ever with AI

Long before software existed, engineers were already struggling with a familiar temptation: adding complexity before it is needed. In software, this became almost proverbial through Donald Knuth’s famous warning: “premature optimization is the root of all evil.” The idea is simple, but often ignored — trying to anticipate every future scenario leads to systems that are harder to build, harder to understand, and ultimately harder to change.

During the Hackathon, this pattern appeared in a modern form. Participants, empowered by AI, could generate large amounts of code almost instantly. And with that speed came a subtle trap: building features, abstractions, and extensions for scenarios that did not yet exist. “We might need this later” became easier to justify when the cost of writing code approached zero. But the fundamental constraint did not change — we still cannot predict the future. What we can do is design systems that are ready to evolve.

AI amplifies this mistake in a nonlinear way. If, traditionally, overengineering was costly and slow, now it is cheap and fast — and therefore more dangerous. It is no longer about writing too much code; it is about generating too much structure without necessity. Layers, abstractions, and configurations appear not because they are required, but because they are easy to produce. The result is a system that feels sophisticated but lacks clarity and purpose.

There is a reason why thinkers like Leonardo da Vinci emphasized simplicity as a sign of mastery: “simplicity is the ultimate sophistication.” In software architecture, this principle remains unchanged. Start with the simplest solution that works. Understand the domain. Deliver value. Then, and only then, evolve the design as new requirements emerge.

The recommendation is straightforward, but not trivial to follow: resist the urge to build for hypothetical futures. Whether using AI or not, focus on clarity, cohesion, and incremental evolution. Good design is not about predicting everything — it is about enabling change with confidence.

Conclusion: AI Is a Multiplier — Make Sure It Multiplies the Right Things

Across this experience, one idea becomes hard to ignore: AI does not change the fundamentals of software engineering — it exposes them. The same principles that guided good engineering before still apply now. The difference is speed. Mistakes that once took weeks to accumulate can now be generated in minutes. And that changes the cost curve dramatically.

At the same time, it is important to be precise: AI is not an enemy. Like any tool in the history of engineering, it can be used well or poorly. We can — and should — use it to:

  • Generate repetitive or boilerplate code
  • Enhance documentation and clarity
  • Explore alternatives and ask questions quickly

But the responsibility for decisions remains ours. Artificial intelligence still depends on natural intelligence to orchestrate it. Without that guidance, it produces output; with it, it produces value.

If there is one practical takeaway, it is this: learn your tools before relying on them. Whether you are working with Jakarta EE, Java, or another ecosystem, understanding modern abstractions, specifications, and best practices enables you to guide AI rather than be guided by it. Without that foundation, prompts become guesses, and generated code becomes a liability. With it, AI becomes a powerful accelerator aligned with your architectural intent.

Finally, there is an almost paradoxical lesson: in an era where generating code is easier than ever, simplicity becomes more valuable, not less. The ability to reduce complexity, to design clear systems, and to evolve them incrementally is still the mark of a mature engineer. AI can help you move faster — but only discipline ensures you are moving in the right direction.

AI Engineering Software engineering

Opinions expressed by DZone contributors are their own.

Related

  • How AI Is Transforming Software Engineering and How Developers Can Take Advantage
  • AI Augmented Software Engineering: All You Need to Know
  • Responsible AI Is an Engineering Problem, not a Policy Document
  • Why Human-in-the-Loop Still Matters in AI-Assisted Coding

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook