devxlogo

An Introduction to Antipatterns in Java Applications

An Introduction to Antipatterns in Java Applications

n antipattern is a classified bad design; in other words, it is the opposite of a design pattern that suggests good design. Antipatterns present bad solutions in a manner that makes it easy for concerned persons to understand the underlying problems and their consequences. While it is important to know design patterns, I believe it is equally important, if not more so, to understand antipatterns.

Let me justify that position. The world of software revolves around maintenance of applications. Of course, each software product’s lifecycle starts with construction, but after the initial roll out, it needs to be maintained. Depending on the development team’s skill, the product might have either a good or a bad design, with the terms good and bad here applied within a context, because a perfect design can qualify as an antipattern when applied in the wrong context. For example, using a Singleton object might be appropriate in a single-application server environment, but it can actually create issues if not handled properly in a clustered-application server environment. In contrast to a positive design pattern, antipatterns elicit a negative solution or legacy approach (a yesteryear’s solution might be an antipattern in today’s world) that could be the result of a team member’s lack of sufficient information or bad judgment in approaching the design or solving an issue.

The real problem begins only after the product is in production and has entered maintenance mode. A maintenance developer who hasn’t worked on the product before might introduce a ‘bad design’ element into the project. But if such bad practices are codified as antipatterns, they give developers a way to recognize them in advance and avoid the most common pitfalls, just as design patterns codify and provide developers with a way to recognize common useful techniques. Following this logic, an antipattern is a ubiquitous bad design worthy of documenting.

This approach is particularly noteworthy when working with a technology like J2EE. J2EE’s initial design philosophy stressed simplicity, but it has morphed into monstrous complexity. In this complex context, both patterns and antipatterns provide a common vocabulary to software managers, architects, designers, and developers alike.

Whether in construction or in maintenance mode, knowledge of antipatterns is imperative for success. Once recognized, developers can refactor these negative patterns to eradicate the bad design, and meliorate the software in general.

This article describes antipatterns from the software architecture and development perspectives. Then it elicits some antipatterns prevalent in the most common layers (user interface, persistence, EJB, etc.) of a J2EE application. The overall goals are to provide background for these antipatterns and suggest solutions to avoid them. To get more detail about any particular pattern, one good source is the book “AntiPatterns: Refactoring Software, Architectures, and Projects in Crisisby William J Brown et al.

Table 1 lists three common design, development, and architecture antipatterns described in this article.

Table 1. Common Antipatterns: The table lists three common design, development, and architecture antipatterns.

AreaCommon Antipatterns
DesignProgramming to concrete classes rather than interfaces
Coupling logic like logging, security and caching in code
DevelopmentGolden Hammer
Input Kludge
ArchitectureReinvent the wheel
Vendor lock-in

Common Antipatterns
The antipatterns listed in this section cut across wide swaths of developer territory.

Programming to Concrete Classes Rather than Interfaces
This is an important design principle, and is often broken. Programming to an interface than a concrete class provides innumerable benefits. You will not be tied into using a specific implementation, and there will be a provision for changing behavior at runtime. The word “interface” implies either a Java interface or an abstract class. As long as you can apply polymorphism, the application’s behavior isn’t locked into specific code. Note that this principle is not applicable in situations when you know that the behavior is not going to change.

An example of programming to an implementation would be:

   Dog animal = new Dog();   animal.bark();   

In contrast, programming to an interface would be:

   Animal animal = getAnimal(Dog.class);   animal.makeNoise();

There are two differences here. The second version uses a factory method to obtain an instance of the Dog class dynamically. Also, note that the second version generalizes the bark() method, which is a Dog-specific implementation of the makeNoise() method that any animal can implement. The following code shows the getAnimal() method from an AnimalFactory class along with a Dog class that exemplifies a specific Animal implementation.

   getAnimal(Class c)   {      if (c == Dog.class)    return new Dog();          // other type tests here   }      class Dog   {      makeNoise()      {         bark();      }   }

Excessive Coupling
When coding, one basic software concept to keep in mind is that less coupling is generally better. For example, if you code a method to perform a certain task, it should perform only that task, helping to keep the code clean, readable and maintainable. But inevitably, certain aspects such as logging, security, and caching tend to creep in. There are, fortunately, good ways to avoid this. One such technique, Aspect Oriented Programming (AOP), provides a neat way to achieve this by injecting the aspect’s behavior into the application at compile time. For more info, refer to the DevX AOP articles in the related resources section of this article.

Development: The Golden Hammer
Excessive or obsessive use of a technology or pattern leads to the Golden Hammer antipattern. Teams or individuals well versed in a particular technology or software tend to use that knowledge for any other project of similar nature?even when another technology would be more appropriate. They tend to see unfamiliar technology as risky. In addition, it’s easier to plan and estimate with a familiar technology. Expanding the knowledge of developers through books, training, and user groups (such as Java User Groups) can be beneficial in avoiding this antipattern.

Input Kludge
Software that mishandles simple user inputs is an Input Kludge. A Web site that asks users to logon via an ID and password, for example, should accept as input only characters that are valid for the ID and password. If the site logic rejects invalid input, it is fail-safe in that respect, but if it doesn’t, unpredictable results can occur. An Input Kludge is an antipattern that can be easily found by the end user, but not so easily during unit testing by the developer.

You can use a monkey test to detect an input kludge problem. Although it is outside the scope of this article to explain monkey testing methodologies in detail, in short, monkey testing refers to automated testing done randomly without any “typical user” bias.

Architecture Antipatterns
These patterns apply broadly to application architecture.

Reinventing the Wheel
This term doesn’t need any description. When developing software, you usually have two choices?build on top of an existing technology or start from scratch. While both can be applicable in different situations, it is nevertheless useful to analyze existing technology before reinventing the wheel to develop functionality that already exists and can satisfy the requirements. This saves time, money as well as leveraging knowledge that developers already might have.

Vendor lock-in
This occurs when the software is either partly or wholly dependent on a specific vendor,. One advantage of J2EE is its portability, but it still gives vendors the opportunity to provide rich proprietary features. Assuredly, such features can aid during development, but they can also have a reverse impact at times. One such problem is loss of control. You’re probably familiar with the feeling that the feature you need is always six months away. Another such problem is when vendor changes to the product break your software, degrade interoperability, force constant upgrades, etc.

One solution is to provide an isolation layer on top of the proprietary stuff. For example, Commons Logging allows you to plug in any logging framework such as Log4J or Java Logging.

J2EE-specific Antipatterns
Table 2 lists few antipatterns for common application layers. To obtain a more complete list of antipatterns in each layer, refer to AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis.

Table 2. Common J2EE Antipatterns: The table lists antipatterns commonly found in the various layers of J2EE applications.

LayerAntipattern
PersistenceDredge
Stifle
JSPToo much session data
Embedded Navigational Information
ServletCommon Functionality in Every Servlet
Accessing Fine-grained EJB Interfaces
EJBsLarge Transactions
Overloading queues in JMS
Web servicesAssuming SOA = Web service
J2EEHardcoded JNDI lookups
Not leveraging EJB container

The next page of this article describes the antipatterns listed in Table 2 for each application layer.

Persistence Antipatterns
Working with applications that retrieve data from databases poses interesting challenges.

Dredge
For example, consider shallow vs. deep data queries. Developers commonly prefer to perform one expensive (deep) lookup, loading more than the required information in one operation over many partial (shallow) lookups of data required for display or reporting. That strategy works in some situations, but if not planned carefully, it can easily cause poor performance and high memory consumption issues.

Stifle
J2EE applications involve interaction between several network resources?databases being one of them. When interacting with databases, applications should be tuned to consume only the optimum network bandwidth; otherwise, the result is likely to be scalability issues. Software applications may become bottlenecked by not optimizing the network pipeline for database communications, and may also choke the network itself.

JSP and Servlet Antipatterns
Some antipatterns are specific to JSP and servlet development.

Overusing Session Data
Using the JSP session as a common data space is an easy temptation to fall prey to. The session provides a simple mechanism for storing transferring data. But an increase in site traffic and the accompanying increase in session data can cause crashes. Also, a careless dump in the session might result in conflicting keys, resulting in application errors. An even worse situation would be an error that shared data across sessions when it is not meant to be shared, potentially exposing sensitive user information.

Even without such technical issues, it is a bad idea to allow bloated sessions. Session data should be used only for information required for workflow across multiple user interfaces, and the session should be cleaned up when the information is no longer needed.

Embedded Navigational Information
This occurs when developers hardcode or embed links to other JSPs. If the page names change, someone must search for and change all the referring links in other pages. Designing a carefully thought-out navigational scheme initially can prevent this maintenance nightmare at a later stage.

Common Functionality in Every Servlet
Hard-coding functionality that is common across multiple servlets in an application makes it hard to maintain. Instead, developers should remove the repeated code from the servlets. Using a framework such as Struts provides a neat way of specifying application flow in an XML file?which can be altered without altering the servlet code. Other common techniques exist as well, such as Front Controller and filters that you can use to remove hard-coded values at different levels.

Accessing Fine-grained EJB Interfaces
Marshaling and unmarshalling data involved in a remote call across the network can cause major time latency in applications. Latency can also occur due to multiple remote calls. Based on the needs on the application, the solution might involve redesigning the EJBs, but if that’s not the case, servlets should not remotely access an EJB entity via a fine-grained API. If the EJB’s expose both fine and coarse-grained APIs then servlets should use single calls to the coarse-grained API in preference to making multiple calls to the fine-grained methods.

EJB Antipatterns
These antipatterns involve enterprise-level concerns such as transactions and message queues.

Large Transactions
Transactions that involve complicated processes that invoke multiple resources can lock out other threads waiting for the same resources for significant time periods. This can cause impact on performance. It’s generally prudent to break such transactions into smaller chunks (depending on the application’s needs), or to use alternatives such as stored procedures for lengthy database processes.

Overloading Queues in JMS
JMS provides both queue and topic destinations. A queue can house different kinds of messages, for example, map (binary) messages or text-based messages. But if you take advantage of this feature to send different kinds of messages on the same queue, it becomes the onus of the consumer to distinguish between them. A better solution would be to separate them out. You can accomplish that programmatically or by using two separate destinations, depending on the application requirements.

Web Services Antipatterns
With the rise of Web services, specific antipatterns will become apparent.

Assuming SOA = Web Service
The term Service Oriented Architecture (SOA) is often confused with Web services, but the fact is that SOA concepts were around long before Web services were born. SOA strives for loose coupling between components. It provides a software service that is consumed by another software service. Lately however, SOA has become synonymous with Web services. Just keep in mind that it’s is entirely possible to base SOA on other service technologies as well.

More Antipatterns
A few other antipatterns that don’t fit neatly into a category are worth mentioning.

Hard-coding JNDI Lookups
The Java Naming and Directory Interface provides a convenient way to look up object information, such as the location of an EJB interface. But JNDI lookup operations are generally expensive, especially if performed repeatedly. If you cache the results though, you can obtain significant performance improvements. One such caching scheme is to use a singleton class.

But networks aren’t static. As the network changes, even the lookups can change. This means that for each network change, a programmer needs to revisit the application code, make any required changes, and recompile/redeploy the code. An alternative would be to provide configurable lookups via an XML configuration file so that developers don’t have to recompile the code every time a network change occurs.

Failing to Leverage EJB Container Features
When developing enterprise components, there are two alternatives?use the services the container provides or write your own services. Although there are numerous alternatives such as Spring, EJBs are still used widely in conjunction with these frameworks. When using EJBs, you should attempt to use the container’s services, such as clustering, load balancing, security, transactions management, fault tolerance, and data storage. Failing to leverage the rich features of the container where possible will eventually result in reinventing the wheel (an architecture antipattern mentioned earlier in this article).

I’m sure you’ve realized the significance of antipatterns in juxtaposition with design patterns. Even if you weren’t already aware of the names of some of the antipatterns described in this article, you should be able to recognize their features and the problems they can cause. Classifying and naming these antipatterns provides the same benefits as classifying and naming design patterns;doing so gives software managers, architects, designers, and developers a common vocabulary and helps them recognize possible sources of error or maintenance headaches in advance.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist