Imports and Static Imports

Use single-type, wildcard and static imports deliberately, settle name clashes such as the two Date classes, and keep imports readable in reviews.

Syntaximport java.util.List; // one type import java.util.*; // every public type in a package import static java.util.Map.entry; // one static member import static org.assertj.core.api.Assertions.*; // all static members

An import is a naming shortcut for the compiler and nothing else. import java.util.List; lets you write List instead of java.util.List. It loads no code, adds no dependency and costs nothing at run time; the class file records the full name either way. Types in java.lang (String, Math, Integer) and types in your own package are visible without an import.

Single-type and on-demand

import java.util.List;   // single-type import
import java.util.*;      // on-demand: every public type in java.util

A wildcard covers one package only. import java.util.*; does not bring in java.util.concurrent.ConcurrentHashMap; that needs its own import. Wildcards do not slow anything down, but they hide where a name comes from, and a library upgrade that adds a class with the same simple name can break a file that did not change. Google's Java style guide does not allow them. IDEs write and tidy explicit imports for you, so most teams leave that job to the IDE.

Name clashes

The classic clash is Date: java.util.Date and java.sql.Date. With java.util.* and java.sql.* both imported, the compiler only complains when you use the name: "reference to Date is ambiguous". A single-type import beats any wildcard, so import the one you use most and write the other with its full name. Two single-type imports of the same simple name are a compile error; one of them has to be written out in full.

Back-end code meets the same problem with framework types. Both Spring and Jakarta EE have a @Transactional, and several libraries ship a class called Logger or Response. When behaviour looks wrong, check which one the import line actually names.

Static imports

import static brings in static methods, fields and enum constants, so you can use them without the class name:

import static java.util.stream.Collectors.toMap;
import static java.util.concurrent.TimeUnit.SECONDS;

They read well when the member name explains itself: assertThat(...), toMap(...), entry("a", 1), SECONDS. They read badly when the name needs its class: a bare of(...) leaves the reader guessing whether it is List.of, Set.of or your own method. Keep them for tests, collectors and well-known constants.

One trap: a method declared in your class hides a statically imported method with the same name, even when the parameters differ. The call then fails with "cannot be applied to given types" instead of quietly using the import.

Example

Example · java
import java.sql.Timestamp;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        // Date means java.util.Date because of the single-type import
        Date legacy = new Date(0);
        // The other Date is written out in full
        java.sql.Date day = java.sql.Date.valueOf("2026-03-01");
        Timestamp createdAt = Timestamp.valueOf("2026-03-01 09:30:00");

        System.out.println(legacy.getClass().getName());
        System.out.println(day.getClass().getName() + " " + day);
        System.out.println(createdAt);
    }
}

// Output:
// java.util.Date
// java.sql.Date 2026-03-01
// 2026-03-01 09:30:00.0

When to use it

  • Use java.util.Date and java.sql.Timestamp in one DAO without an ambiguity error.
  • Statically import AssertJ's assertThat so every test assertion reads as one line.
  • Check that a service imports Spring's @Transactional and not a same-named annotation from another library.

More examples

Static imports that read well

entry, SECONDS and joining are clear without their class names, so the static imports make the code shorter without hiding anything. Static imports conventionally go in their own block above the normal imports.

Example · java
import static java.util.Map.entry;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.joining;

import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> requestsPerMinute = Map.ofEntries(entry("free", 60), entry("team", 600));
        long windowMs = SECONDS.toMillis(45);

        String plans = List.of("free", "team").stream()
                .map(plan -> plan + "=" + requestsPerMinute.get(plan))
                .collect(joining(", "));
        System.out.println(plans);
        System.out.println(windowMs);
    }
}

// Output:
// free=60, team=600
// 45000

Settling an ambiguous wildcard

Remove the third import and the file stops compiling with "reference to Date is ambiguous". The last line shows how tightly the two packages are tied: java.sql.Timestamp, like java.sql.Date, extends java.util.Date, so legacy JDBC code often needs both names.

Example · java
import java.sql.*;
import java.util.*;
import java.util.Date; // a single-type import beats both wildcards

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("b", "a"));
        Collections.sort(names);
        Date stamp = new Date(0);
        Timestamp ts = new Timestamp(0);
        System.out.println(names);
        System.out.println(stamp.getClass().getName());
        System.out.println(ts.getClass().getName() + " extends " + ts.getClass().getSuperclass().getName());
    }
}

// Output:
// [a, b]
// java.util.Date
// java.sql.Timestamp extends java.util.Date

Static imports in a test class

Tests are where static imports earn their keep: assertThat reads like a sentence and every test file uses it. Keeping the test in the production class's package lets it use package-private types without an import.

Example · java
package com.example.shop.orders;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.math.BigDecimal;
import java.util.List;
import org.junit.jupiter.api.Test;

class OrderTotalsTest {

    // OrderTotals is in the same package, so it needs no import
    private final OrderTotals totals = new OrderTotals();

    @Test
    void addsLineAmounts() {
        assertThat(totals.sum(List.of(new BigDecimal("2.50"), new BigDecimal("1.25"))))
                .isEqualByComparingTo("3.75");
    }

    @Test
    void rejectsNegativeAmounts() {
        assertThatThrownBy(() -> totals.sum(List.of(new BigDecimal("-1"))))
                .isInstanceOf(IllegalArgumentException.class);
    }
}

Discussion

  • Be the first to comment on this lesson.