Useful JavaExtra· 35 min read

Generics (Type-Safe Containers)

Generics let one class or method work with any type while keeping it type-safe — the magic behind ArrayList<String>.

What you will learn

  • Read the angle-bracket type syntax
  • Write a simple generic class and method
  • See how generics prevent type mistakes

You have already used generics

Every time you wrote ArrayList<String> or HashMap<String, Integer>, you used generics. The part in the angle brackets < > tells the container what type it will hold. Generics are the feature that lets one piece of code safely work with many different types.

Why does this matter? Because it makes the compiler check your types for you. A list declared ArrayList<String> will only ever accept Strings — try to add a number and Java refuses to compile, catching the mistake early.

The <String> guarantees the list only holds Strings
import java.util.ArrayList;

ArrayList<String> names = new ArrayList<>();
names.add("Asha");
names.add("Ravi");
// names.add(42);   // would NOT compile — only Strings allowed

String first = names.get(0);    // no casting needed — Java knows it is a String
System.out.println(first.toUpperCase());

Note: Output: ASHA Because the list is ArrayList<String>, get(0) is guaranteed to be a String, so we can call toUpperCase() on it directly. The commented-out add(42) would be rejected at compile time — that safety is exactly what generics give you.

Writing your own generic class

You can put generics on your own classes too. The trick is a type parameter — a placeholder name (by convention a single capital letter like T, short for Type) that stands in for whatever real type the user picks later.

Here is a tiny Box that can safely hold one item of any type. Whoever creates the Box decides the type by filling in the angle brackets.

A generic Box that works with any type T
public class Box<T> {        // T is a placeholder for a real type
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}

Note: Output: (No output yet — this is the class. T is not a real type; it is a stand-in. When someone writes Box<String>, every T becomes String. Write Box<Integer> and every T becomes Integer. One class, any type.)

Using the generic class

The same Box class, used with two different types
public class Main {
    public static void main(String[] args) {
        Box<String> wordBox = new Box<>();
        wordBox.set("Hello");
        System.out.println(wordBox.get().toUpperCase());

        Box<Integer> numberBox = new Box<>();
        numberBox.set(7);
        System.out.println(numberBox.get() * 2);
    }
}

Note: Output: HELLO 14 The first Box holds a String, so get() returns a String we can uppercase. The second holds an Integer, so get() returns a number we can double. We wrote the Box class once, yet it stayed type-safe for both. Without generics we would have used a plain Object and had to cast (and risk mistakes) every time we read it.

A generic method

A single method can also be generic. You declare the placeholder <T> just before the return type. This method prints any single item, whatever its type:

One generic method that accepts any type
public class Util {
    static <T> void printItem(T item) {
        System.out.println("Item: " + item);
    }

    public static void main(String[] args) {
        printItem("Java");
        printItem(2026);
        printItem(true);
    }
}

Note: Output: Item: Java Item: 2026 Item: true The <T> before void means this method works for any type. We called it with a String, an int and a boolean, and each time T quietly became that type. One method, many types, all type-checked.

Why generics earn their keep

  • Type safety — mistakes (adding the wrong type) are caught while you compile, not as a crash later.
  • No manual casting — Java already knows the type, so reading values is clean.
  • Reusable code — write a container or method once, use it for every type.

Tip: By convention, single capital letters are used for type parameters: T for type, E for element, K and V for the key and value of a map (that is why a map is written HashMap<K, V>). They are just names — you could call yours anything, but stick to the convention so others recognise it.

Q. What is the main benefit of declaring ArrayList<String> instead of a plain list?

Answer: The <String> is a generic type argument. It locks the list to Strings, so adding a wrong type fails to compile and reading items needs no cast — that type safety is the whole point of generics.

✍️ Practice

  1. Create a Box<Double>, put a price in it, and print the value returned by get().
  2. Write a generic method firstOf(T[] items) that returns the first element of any array and test it with a String array.

🏠 Homework

  1. Write a generic class Pair<A, B> that holds two values of possibly different types, with getters for each. Create a Pair<String, Integer> (a name and an age) and print both.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →