Generics in Java: Type Parameters, Bounds, Wildcards and Runnable Examples
Build type-safe Java containers, then trace generic methods, upper bounds, wildcard reads and writes, invariance, erasure, and common failure cases.
KnowledgeGate Team
Exam prep & CS education

A collection that accepts anything looks flexible until a value returns with the wrong type and fails at runtime. One runnable GenericsDemo.java uses Box<String>, Box<Integer>, Pair<String, Integer>, marks [12, 18, 30], and a number sink that changes from [2.5] to [2.5, 7, 11]. It demonstrates type parameters, invariance, wildcards and erasure.
Why generics move a type error to the compiler
A generic declaration uses a type parameter. In Box<T>, T is the placeholder; Box<String> supplies the type argument. Its set() accepts a String, and get() returns one without a cast.
Box<String> topic = new Box<>("Generics"); topic.set(99); is a compile-time error because 99 is an Integer. With the raw type, Box raw = new Box("Generics"); raw.set(99); String text = (String) raw.get(); compiles with warnings, but the cast throws ClassCastException. Unlike Box<Object>, raw Box discards generic checking.
The Coding & Skill Development Courses path groups programming and data-structure topics; generic containers make reusable code type-safe.
Build and run one complete generic program
Save this program as GenericsDemo.java. A class declares type parameters after its name; a generic method declares <T> before its return type.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
final class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T get() {
return value;
}
void set(T value) {
this.value = value;
}
}
final class Pair<K, V> {
private final K key;
private final V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
K getKey() {
return key;
}
V getValue() {
return value;
}
}
public class GenericsDemo {
static <T> T first(List<T> items) {
if (items.isEmpty()) {
throw new IllegalArgumentException("empty list");
}
return items.get(0);
}
static double sum(List<? extends Number> values) {
double total = 0.0;
for (Number value : values) {
total += value.doubleValue();
}
return total;
}
static <T extends Number> double average(List<T> values) {
if (values.isEmpty()) {
throw new IllegalArgumentException("empty list");
}
return sum(values) / values.size();
}
static void addDefaults(List<? super Integer> target) {
target.add(7);
target.add(11);
}
public static void main(String[] args) {
Box<String> topic = new Box<>("Generics");
Box<Integer> attempts = new Box<>(3);
Pair<String, Integer> result = new Pair<>("Asha", 88);
List<Integer> marks = new ArrayList<>(Arrays.asList(12, 18, 30));
List<Number> sink = new ArrayList<>();
sink.add(2.5);
addDefaults(sink);
System.out.println(topic.get());
System.out.println(attempts.get());
System.out.println(result.getKey() + " -> " + result.getValue());
System.out.println(first(marks));
System.out.println(sum(marks));
System.out.println(average(marks));
System.out.println(sink);
}
}Compile and run it:
javac GenericsDemo.java
java GenericsDemoThe exact output is:
Generics
3
Asha -> 88
12
60.0
20.0
[2.5, 7, 11]The arithmetic is 12 + 18 + 30 = 60, so sum returns 60.0. With three marks, 60 / 3 = 20.0.

Read type parameters, arguments and inference
Box<T> has one class type parameter; Pair<K, V> has two. In static <T> T first(List<T> items), method-level T appears before the return type. From first(marks), the compiler infers Integer and returns 12.
The diamond uses context. In new Box<>("Generics"), the left side supplies Box<String>. Box<Number> n = new Box<>(3) is valid because Integer fits Number; Box<Integer> n = new Box<>(2.5) is invalid because 2.5 is a Double.
Names such as T, E, K and V are conventions. For key-value structures related to Pair<K, V>, see Hashing and Collision Resolution.
Put an upper bound on a type and use extends
static <T extends Number> double average(List<T> values) uses an upper bound. T can be Integer, Double, or another Number subtype, so the code can use Number operations through sum.
For marks, inference gives T = Integer: 12.0 + 18.0 + 30.0 = 60.0, the count is 3, and 60.0 / 3 = 20.0. average(Arrays.asList(1.5, 2.5)) infers T = Double and returns (1.5 + 2.5) / 2 = 2.0.
List<? extends Number> means a list of an unknown Number subtype, suitable for reading numeric values. sum(marks) reads 12, 18 and 30 as Number and returns 60.0.
After List<? extends Number> source = marks, Number firstValue = source.get(0) gives 12, but source.add(40) fails because the element type is unknown. The list is not necessarily immutable; reads and some removals remain available.
Understand invariance and apply PECS
List<Integer> is not a subtype of List<Number>. If List<Number> numbers = marks were legal, code could add 2.5 and violate the original List<Integer> contract. The assignment fails although Integer is a Number.
PECS gives the direction. List<? extends Number> produces values readable as Number but cannot accept 40. List<? super Integer> consumes integers: the List<Number> sink starts as [2.5], accepts 7 and 11, and ends as [2.5, 7, 11].
From List<? super Integer>, a read is known only as Object because the list could hold Integer, Number, or Object. Stacks and Queues: Operations and Uses provides another setting for producer-consumer thinking.

Know what type erasure changes and forbids
Generic checks happen at compile time. Runtime does not create separate classes for Box<String> and Box<Integer>, so topic.getClass() == attempts.getClass() is true. The compiler inserts casts and bound checks; some declarations remain as class-file metadata.
Erasure also explains several restrictions:
new T()is illegal. Accept a factory or an already-created value.new T[5]andnew List<String>[5]are illegal. Prefer a generic collection.value instanceof List<String>is illegal, whilevalue instanceof List<?>is allowed.A static field cannot use the class's
Tbecause it belongs to the class, not an instance with a chosen argument.Box<int>is illegal because type arguments are reference types. UseBox<Integer>.
Overloads process(List<String> x) and process(List<Integer> x) cannot coexist because both erase to process(List). This clash differs from the raw-type ClassCastException above.
Diagnose common errors and practise assessment shapes
Match each symptom to its fix:
topic.set(99)fails because the box requiresString. Pass a string or use another box.List<Number> widened = marksfails because lists are invariant. Use a suitable wildcard view.source.add(40)fails becauseextendshides the element type. Add through the originalList<Integer>when valid.Integer x = consumer.get(0)fails because asuperread is known only asObject.A raw
Boxmay compile with warnings but loses safety. Parameterise it.
Assessments may ask you to place <T>, infer a type, test compilation, choose extends or super, predict a raw failure, or find an erasure clash.
Try these before checking the answers:
For
Box<Double> rate = new Box<>(2.5);, give the static type, returned type, and value.Evaluate
first(Arrays.asList("Java", "DSA")).Evaluate
sum(Arrays.asList(4, 6, 10)).Choose a parameter type that reads both
List<Integer>andList<Double>as numbers without adding.
Answers
Static type
Box<Double>, return typeDouble, value2.5.Stringvalue"Java".20.0, because4 + 6 + 10 = 20.List<? extends Number>.
Short version and the next Java step
Declare T, infer or supply its argument, bound it when operations need a common parent, remember invariance, read from extends, write to super, and account for erasure. Change marks to [5, 15, 25, 35], then predict first = 5, sum = 80.0, and average = 20.0 before running. Continue with the Java Course: Concepts, MCQs & Coding Questions, then apply type-safe containers in the DSA using Java course.
Keep learning

Polymorphism in Java: Overloading, Overriding and Runnable Examples
Learn how Java chooses overloaded methods at compile time and overridden methods at runtime. Trace two complete programs, common traps and practice targets.

Packages and Access Modifiers in Java: Visibility Rules with Runnable Examples
Learn how Java package boundaries change member visibility. Compile a complete example covering public, protected, package-private and private access.

OOP Basics in Java: Classes, Objects and the Four Pillars with Runnable Examples
Connect Java's OOP syntax to design decisions with a complete Account example. Trace two objects through validation, overriding and runtime dispatch to exact output.

Classes and Objects in Java: Build and Trace Two Objects Step by Step
Build a Java Student class, create two independent objects, and trace fields, methods, aliases, null references, output, and common errors step by step.