Generics bring type safety, flexibility, and reusability to Java. But when it comes to public API design, wildcards (?
, ? extends
, ? super
) play a critical role. They allow developers to balance flexibility with safety, ensuring APIs are usable in real-world applications without compromising maintainability.
In this tutorial, we’ll explore effective use of wildcards in public APIs, how the PECS principle guides design choices, and best practices to avoid pitfalls.
Core Definition: What Are Wildcards?
Wildcards (?
) represent unknown types in generics. They are particularly useful in public-facing methods, where APIs must work with a variety of parameterized types without exposing rigid type constraints.
Three Variants of Wildcards
?
— Unknown type (unbounded wildcard)? extends T
— Upper-bounded wildcard (producer of data)? super T
— Lower-bounded wildcard (consumer of data)
Example: Without Wildcards (Rigid API)
public void copy(List<Object> dest, List<Object> src) {
for (Object o : src) {
dest.add(o);
}
}
This method only works with List<Object>
but not with List<String>
or List<Integer>
, making it impractical for public APIs.
Example: With Wildcards (Flexible API)
public <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) {
dest.add(t);
}
}
- Source:
? extends T
→ producer of elements. - Destination:
? super T
→ consumer of elements.
This follows the PECS principle: Producer Extends, Consumer Super.
Unbounded Wildcards (?
)
Use Case: Read-Only Access
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
- Works with
List<String>
,List<Integer>
, etc. - Prevents modifications → safe for read-only APIs.
Upper-Bounded Wildcards (? extends T
)
Use Case: APIs that consume data from the list
public double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number n : numbers) total += n.doubleValue();
return total;
}
- Accepts
List<Integer>
,List<Double>
, etc. - Perfect for data producers.
Lower-Bounded Wildcards (? super T
)
Use Case: APIs that insert data into the list
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
- Accepts
List<Integer>
,List<Number>
, orList<Object>
. - Perfect for data consumers.
Wildcards in Collections Framework
The Java Collections API uses wildcards extensively:
Collection<? extends E>
inCollections.unmodifiableList
(read-only producer).Collection<? super E>
inCollections.addAll
(consumer of elements).Comparator<? super T>
in sorting methods (Collections.sort
).
Raw Types vs Wildcards
- Raw types lose type safety → can cause
ClassCastException
. - Wildcards preserve flexibility and type safety in APIs.
Type Inference and Wildcards
Java’s type inference (diamond operator <>
) works seamlessly with wildcards, especially when combined with generic methods.
Map<String, List<? extends Number>> map = new HashMap<>();
Effective Public API Design with Wildcards
- Use wildcards to maximize usability of public methods.
- Prefer bounded wildcards (
? extends
,? super
) for clarity. - Avoid exposing raw types.
- Document the intended usage of wildcards (producer vs consumer).
Case Study: Flexible Repository Pattern
interface Repository<T> {
void save(T entity);
List<? extends T> findAll();
void deleteAll(List<? super T> entities);
}
findAll
uses? extends T
(produces entities).deleteAll
uses? super T
(consumes entities).
Best Practices
- Follow PECS: Producer Extends, Consumer Super.
- Use unbounded wildcards for read-only parameters.
- Avoid wildcards in return types → prefer type parameters.
- Do not mix raw types with wildcards.
- Keep APIs simple → don’t overuse wildcards.
Common Anti-Patterns
- Returning wildcards from public APIs.
- Overloading methods unnecessarily with different wildcard bounds.
- Excessive nesting of wildcards → hurts readability.
Performance Considerations
- Wildcards themselves have no runtime overhead (due to type erasure).
- Reflection + wildcards can add complexity in frameworks.
📌 What's New in Java for Generics?
- Java 5: Introduction of Generics and wildcards.
- Java 7: Diamond operator (
<>
) improved usability. - Java 8: Wildcards integrated with lambdas and streams.
- Java 10:
var
simplified wildcard-heavy code. - Java 17+: Pattern matching works better with generics.
- Java 21: Virtual threads benefit from generic/wildcard-based APIs.
Conclusion and Key Takeaways
- Wildcards provide flexibility and safety in public APIs.
- Use
? extends
for producers and? super
for consumers. - Avoid raw types and overly complex wildcard nesting.
- Proper wildcard use leads to cleaner, safer, and more reusable APIs.
FAQ on Wildcards in Public APIs
Q1: Why not just use raw types instead of wildcards?
Raw types break type safety; wildcards preserve it.
Q2: Can I return wildcards from a public method?
Avoid this. Return specific types or generics instead.
Q3: What’s the difference between ? extends T
and <T>
?<T>
declares a type parameter; ? extends T
allows flexibility in method arguments.
Q4: When should I use unbounded wildcards?
For read-only parameters where type details don’t matter.
Q5: Can wildcards be nested?
Yes, but keep it minimal (Map<String, List<? extends Number>>
).
Q6: Do wildcards impact runtime performance?
No, due to type erasure, they only affect compile-time checks.
Q7: What if I need both producer and consumer behavior?
Split into two methods: one with ? extends
, one with ? super
.
Q8: Why are wildcards discouraged in return types?
They reduce clarity and usability for clients.
Q9: Which frameworks rely heavily on wildcards?
Collections API, Spring Framework, Hibernate, Guava.
Q10: What’s the one rule I should remember?
PECS: Producer Extends, Consumer Super.