0Pricing
Java Academy · Lesson

Wildcards in APIs

Design flexible generic methods.

Wildcards in APIs is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Designing Flexible Methods

Wildcards are mainly a tool for API designers. A well-placed wildcard lets callers pass more types without casts.

The goal: maximum flexibility for callers, full type safety inside.

import java.util.List;

public class Main {
    static double total(List<? extends Number> items) {
        double sum = 0;
        for (Number n : items) sum += n.doubleValue();
        return sum;
    }
    public static void main(String[] args) {
        System.out.println(total(List.of(1, 2, 3)));
        System.out.println(total(List.of(1.5, 2.5)));
    }
}

Unbounded Wildcards

List<?> is an unbounded wildcard: a list of some unknown type.

Use it when your method does not care about the element type, for example counting elements or checking emptiness.

import java.util.List;

public class Main {
    static int size(List<?> any) {
        return any.size();
    }
    public static void main(String[] args) {
        System.out.println(size(List.of("a", "b")));
        System.out.println(size(List.of(1, 2, 3)));
    }
}

List<?> vs List<Object>

These are different. List<Object> accepts only a list declared as Object. List<?> accepts a list of any type.

You can pass a List<String> to a List<?> parameter, but not to a List<Object> one.

import java.util.List;

public class Main {
    static void printAll(List<?> list) {
        for (Object o : list) System.out.println(o);
    }
    public static void main(String[] args) {
        List<String> names = List.of("Ada", "Linus");
        printAll(names); // works; List<Object> would reject this
    }
}

When a Type Parameter Is Clearer

If you need to refer to the element type more than once, a named type parameter reads better than a wildcard.

Compare void swap(List<?> l, int i, int j) (needs a private helper) with a clean generic version.

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void swap(List<T> list, int i, int j) {
        T tmp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, tmp);
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(List.of("a", "b", "c"));
        swap(list, 0, 2);
        System.out.println(list);
    }
}

The Helper Capture Trick

If a public API must use List<?>, you can delegate to a private generic helper to capture the wildcard.

The helper names the type T so it can write back into the list.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void reverse(List<?> list) { reverseHelper(list); }
    private static <T> void reverseHelper(List<T> list) {
        for (int i = 0, j = list.size() - 1; i < j; i++, j--) {
            T tmp = list.get(i);
            list.set(i, list.get(j));
            list.set(j, tmp);
        }
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
        reverse(list);
        System.out.println(list);
    }
}

Producer Parameters in a Real API

Designing an addAll that pulls from any compatible source uses an upper bound on the source.

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Main {
    static <T> void addAll(List<T> target, Collection<? extends T> source) {
        for (T t : source) target.add(t);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        addAll(nums, List.of(1, 2, 3));
        System.out.println(nums);
    }
}

Consumer Parameters in a Real API

A sink API that writes results uses a lower bound on the destination so callers can supply broader containers.

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void repeat(T value, int times, List<? super T> sink) {
        for (int i = 0; i < times; i++) sink.add(value);
    }
    public static void main(String[] args) {
        List<Object> out = new ArrayList<>();
        repeat("hi", 3, out);
        System.out.println(out);
    }
}

Wildcards With Nested Generics

Nested generics often need wildcards. A method accepting a list of any kind of list uses List<? extends List<?>>.

import java.util.List;

public class Main {
    static int countAll(List<? extends List<?>> lists) {
        int total = 0;
        for (List<?> inner : lists) total += inner.size();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(countAll(List.of(List.of(1, 2), List.of("a", "b", "c"))));
    }
}

Avoiding Over-Generification

Not every method needs wildcards. If callers only ever pass one type, a plain parameter is clearer.

Add wildcards when you observe real callers being blocked by invariance, not preemptively.

import java.util.List;

public class Main {
    // Simple and clear; no wildcard needed for this internal use
    static String joinStrings(List<String> parts) {
        return String.join(", ", parts);
    }
    public static void main(String[] args) {
        System.out.println(joinStrings(List.of("a", "b", "c")));
    }
}

Documenting Wildcard Intent

A wildcard signals intent. ? extends T tells readers "I only read this". ? super T says "I only write this".

Choosing the right wildcard is a form of self-documenting code that also enforces the contract.

import java.util.ArrayList;
import java.util.List;

public class Main {
    // src: read-only producer; dest: write-only consumer
    static <T> void transfer(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Number> dest = new ArrayList<>();
        transfer(List.of(1, 2), dest);
        System.out.println(dest);
    }
}

A Generic Stack API

Bloch's famous stack example: pushAll consumes from a producer (extends), popAll produces into a consumer (super).

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.ArrayList;
import java.util.List;

public class Main {
    static class Stack<E> {
        private final Deque<E> items = new ArrayDeque<>();
        void pushAll(Collection<? extends E> src) { for (E e : src) items.push(e); }
        void popAll(Collection<? super E> dst) { while (!items.isEmpty()) dst.add(items.pop()); }
    }
    public static void main(String[] args) {
        Stack<Integer> s = new Stack<>();
        s.pushAll(List.of(1, 2, 3));
        List<Number> out = new ArrayList<>();
        s.popAll(out);
        System.out.println(out);
    }
}

Quick Check

Test your API design instincts.

Recap

You learned to design APIs with wildcards:

  • List<?> for type-agnostic read-only methods.
  • Apply PECS on parameters for flexibility.
  • Use the private helper capture trick when you must write through a wildcard.
  • Avoid wildcards in return types and do not over-generify.

You have completed the wildcards and PECS course.

public class Main {
    public static void main(String[] args) {
        System.out.println("Wildcards in APIs course complete");
    }
}

Frequently asked questions

Is the “Wildcards in APIs” lesson free?

Yes — the full text of “Wildcards in APIs” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.

What will I learn in “Wildcards in APIs”?

Design flexible generic methods. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Wildcards in APIs” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Upper Bounded Wildcards
  2. Lower Bounded Wildcards
  3. The PECS Principle
  4. Wildcards in APIs
← Back to Java Academy