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
TreeMap and SortedMap in Java: Ordering, Range Views and Worked Examples
Learn how Java's TreeMap orders keys, answers floor and ceiling queries, exposes live range views, and handles comparator collisions through worked examples.
HashSet and TreeSet in Java: Runnable Examples and Key Differences
Learn how Java sets handle duplicates, ordering, navigation and custom objects through deterministic examples you can run and modify.
Java Garbage Collection Tutorial: Reachability, Leaks and Runnable Examples
Trace three connected objects, find exactly when each becomes eligible for collection, and repair a cache that keeps byte arrays reachable.
Java Memory Model Tutorial: Happens-Before, volatile and Safe Publication
Learn the Java Memory Model through exact start/join, volatile, synchronized, final-field and AtomicInteger examples, with clear happens-before guarantees.





