This content originally appeared on DEV Community and was authored by Maxi Contieri
When your methods return generic types, you break the call chain
TL;DR: Avoid methods that return Object, Any, or null instead of specific types. Make them fully polymorphic
Problems
- Missed Polymorphism
- Tight Coupling
- Excessive Null Checks
- Confusing Returns
- Fragile Code
- Hard to Test
- Lost type safety
- Ifs Pollution
- Broken polymorphism
- Runtime errors
- Unclear contracts
- Testing difficulties
- Poor maintainability
Solutions
- Return Polymorphic Types
- Use Null Object Pattern
- Avoid Returning any
- Favor Exceptions for Errors
- Rename for Clarity
- Return specific types or Interfaces
- Use proper abstractions
- Create meaningful objects
Refactorings
Context
When you write a method that can return many types, such as an any or a null, you lose polymorphism.
Polymorphism lets you treat objects that share an interface or a base type interchangeably, simplifying your code.
Returning null forces your callers to write extra checks and handle special cases, which clutters the code and increases coupling.
Returning any (or a type that erases actual type information) makes it harder to understand what the method actually returns, causing bugs and confusion.
You force callers to perform type checking and casting.
This breaks the fundamental principle of polymorphism where objects should behave according to their contracts.
Methods should return specific types that clearly communicate their intent and allow the compiler to verify correctness at compile time.
Remember
Two methods are polymorphic if their signatures are the same, the arguments are polymorphic, AND the return is also polymorphic.
Sample Code
Wrong
public class DatabaseConnection {
public Object execute(String sql) {
if (sql.startsWith("SELECT")) {
return new ResultSet();
} else if (sql.startsWith("INSERT")) {
return Integer.valueOf(42);
} else if (sql.startsWith("UPDATE")) {
return Boolean.TRUE;
}
return null;
// The billion dollar mistake
}
}
public class QueryHandler {
public void handle(String sql, DatabaseConnection db) {
Object result = db.execute(sql);
// The caller needs to be aware of many different types
if (result instanceof ResultSet) {
System.out.println("Fetched rows");
} else if (result instanceof Integer) {
System.out.println("Inserted " + result);
} else if (result instanceof Boolean) {
System.out.println("Updated " + result);
} else {
System.out.println("Unknown result");
}
}
}
// This second class has a method execute()
// which is NOT polymorphic since it returns
// another types
public class NonRelationalDatabaseConnection {
public Object execute(String query) {
if (query.startsWith("FIND")) {
return new Document();
} else if (query.startsWith("INSERT")) {
return Integer.valueOf(1);
} else if (query.startsWith("UPDATE")) {
return Boolean.TRUE;
}
return null; // The billion dollar mistake
}
}
Right
interface QueryResult {
void display();
}
class SelectResult implements QueryResult {
public void display() {
System.out.println("Fetched rows");
}
}
class InsertResult implements QueryResult {
private final int count;
InsertResult(int count) { this.count = count; }
public void display() {
System.out.println("Inserted " + count);
}
}
class UpdateResult implements QueryResult {
private final boolean ok;
UpdateResult(boolean ok) { this.ok = ok; }
public void display() {
System.out.println("Updated " + ok);
}
}
class DocumentResult implements QueryResult {
public void display() {
System.out.println("Fetched documents");
}
}
interface DatabaseConnection {
QueryResult execute(String query);
}
public class RelationalDatabaseConnection
implements DatabaseConnection {
public QueryResult execute(String sql) {
// execute() is now polymorphic and returns a QueryResult
if (sql.startsWith("SELECT")) {
return new SelectResult();
} else if (sql.startsWith("INSERT")) {
return new InsertResult(42);
} else if (sql.startsWith("UPDATE")) {
return new UpdateResult(true);
}
// You remove null
throw new IllegalArgumentException("Unknown SQL");
}
}
public class NonRelationalDatabaseConnection
implements DatabaseConnection {
public QueryResult execute(String query) {
// execute() is now polymorphic and returns a QueryResult
if (query.startsWith("FIND")) {
return new DocumentResult();
} else if (query.startsWith("INSERT")) {
return new InsertResult(1);
} else if (query.startsWith("UPDATE")) {
return new UpdateResult(true);
}
throw new IllegalArgumentException("Unknown query");
}
}
public class QueryHandler {
public void handle(String sql, DatabaseConnection db) {
QueryResult result = db.execute(sql);
result.display();
}
}
Detection
[X] Semi-Automatic
Look for methods with return types like Object, Any, void*, or frequent null returns.
Also check for scattered if-null checks or type checks after method calls.
Tooling and static analyzers sometimes warn about methods returning any or null without documentation.
Search for instanceof checks or type casting after method calls.
Watch for methods that return different types based on parameters or their internal state.
Exceptions
Generic collection frameworks
Serialization libraries
Tags
- Polymorphism
Level
[X] Intermediate
Why the Bijection Is Important
When a method always returns a type that aligns with the concept it represents, programmers don’t need special cases.
Breaking this Bijection by returning any or null creates ambiguity.
The calling code must guess the actual type or deal with nulls, increasing bugs and maintenance cost.
Real-world objects have specific types and behaviors.
AI Generation
AI generators sometimes produce methods returning any or null because they prioritize flexibility or simplicity over strong typing and polymorphism.
AI Detection
AI tools can fix this smell when given instructions to enforce typed returns and suggest Null Object or Optional patterns.
They can refactor null returns into polymorphic return hierarchies automatically if guided.
Simple prompts about “improving return types” often help AI suggest better alternatives.
Try Them!
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Replace methods returning Object, Any, or null with specific return types. Create proper abstractions and null object patterns. Ensure type safety and clear contracts
Without Proper Instructions | With Specific Instructions |
---|---|
ChatGPT | ChatGPT |
Claude | Claude |
Perplexity | Perplexity |
Copilot | Copilot |
You | You |
Gemini | Gemini |
DeepSeek | DeepSeek |
Meta AI | Meta AI |
Grok | Grok |
Qwen | Qwen |
Conclusion
Methods should return specific types that clearly communicate their purpose and enable compile-time verification.
When you replace non-polymorphic returns with proper abstractions, you create safer, more maintainable code that expresses its intent clearly.
Relations

Code Smell 11 – Subclassification for Code Reuse
Maxi Contieri ・ Oct 30 ’20

Code Smell 43 – Concrete Classes Subclassified
Maxi Contieri ・ Dec 5 ’20
More Information

How to Get Rid of Annoying IFs Forever
Maxi Contieri ・ Nov 9 ’20

Null: The Billion dollar mistake
Maxi Contieri ・ Nov 18 ’20
Disclaimer
Code Smells are my opinion.
Credits
Photo by Randy Fath on Unsplash
Return the right type, always.
Brian Goetz

Software Engineering Great Quotes
Maxi Contieri ・ Dec 28 ’20
This article is part of the CodeSmell Series.

How to Find the Stinky parts of your Code
Maxi Contieri ・ May 21 ’21
This content originally appeared on DEV Community and was authored by Maxi Contieri