
In diesem Artikel erfährst du:
- Was bewirkt ein Import, und welche Arten von Imports kennt Java?
- Wie können mit
import moduleganze Java-Module importiert werden? - Wie können dabei Mehrdeutigkeiten aufgelöst werden?
- Was sind transitive Modul-Importe, und wie funktionieren sie?
- Welche Module werden standardmäßig importiert?
Beginnen wir mit den klassischen Imports, die es seit Java 1.0 gibt.
Java Imports
Was ein Import bewirkt
Eine Klasse aus einem anderen Paket sprichst du über ihren voll qualifizierten Namen an, also mit vorangestelltem Paketnamen:
public class Names {
java.util.List<String> names = new java.util.ArrayList<>();
}
Ein Import macht eine Klasse unter ihrem einfachen Namen verfügbar. Mit zwei Imports wird aus dem Beispiel:
import java.util.ArrayList;
import java.util.List;
public class Names {
List<String> names = new ArrayList<>();
}
Ein Import lädt keine Klasse und erzeugt keinen Bytecode. Er sagt nur dem Compiler, welche Klasse mit List bzw. ArrayList gemeint ist. Kompilierst du beide Varianten mit javac -g:none (ohne Debug-Informationen, die die Zeilennummern enthalten), sind die .class-Dateien byte-identisch – in beiden stehen die voll qualifizierten Namen java/util/List und java/util/ArrayList.
Und String? Dafür brauchst du keinen Import, denn Java importiert das Paket java.lang automatisch – mehr dazu unter Automatisch importiert: java.lang.
Klassen und Pakete importieren
Seit Java 1.0 können wir mit dem import-Statement einzelne Klassen („single-type-import declaration“) oder ganze Pakete („type-import-on-demand declaration“) importieren, z. B. wie folgt:
import java.util.*;
import java.util.stream.Stream;
Ein Paket-Import umfasst nur die Klassen genau dieses Pakets, nicht die seiner Unterpakete. import java.util.*; importiert also java.util.List, aber nicht java.util.stream.Stream – dafür brauchst du den zweiten Import aus dem Beispiel. Ohne ihn bricht javac mit „cannot find symbol“ ab, sobald du Stream verwendest.
Statische Imports
Seit Java 5 kannst du mit import static auch statische Felder und Methoden einer Klasse importieren – einzeln („single-static-import declaration“) oder alle auf einmal („static-import-on-demand declaration“):
import static java.lang.Math.PI; // ← nur die Konstante PI
import static java.lang.Math.*; // ← alle statischen Member von Math
public class Circle {
double area = PI * pow(2, 2);
}
Ohne die statischen Imports müsstest du Math.PI und Math.pow(2, 2) schreiben.
Automatisch importiert: java.lang
Die Klassen des Pakets java.lang werden seit jeher automatisch importiert. Deshalb müssen wir für Klassen wie Object, String, Integer, Exception usw. keine import-Statements angeben.
Die Java Language Specification beschreibt das so, als stünde in jeder Quelldatei direkt nach der Package-Deklaration import java.lang.*;. Daraus folgt auch: Unterpakete wie java.lang.reflect werden nicht automatisch importiert.
Wo Imports stehen
Eine Java-Quelldatei hat eine feste Reihenfolge: zuerst die optionale Package-Deklaration, dann die Imports, dann die Klassen. Steht ein Import nach der Klasse, bricht der Compiler ab:
public class Order {}
import java.util.List;
Order.java:3: error: class, interface, annotation type, enum, record, method or field expected
import java.util.List;
^
1 error
Dieselbe Meldung erscheint, wenn die Package-Deklaration erst nach einem Import steht.
Gibt es in Java Import-Aliase („import … as“)?
Nein. Anders als z. B. Python oder Kotlin kennt Java keine Imports unter einem anderen Namen. import java.util.List as JList; quittiert javac mit der Meldung ';' expected.
Brauchst du in einer Klasse zwei Klassen mit demselben einfachen Namen, importierst du eine davon und schreibst die andere voll qualifiziert:
import java.util.List;
public class TwoLists {
List<String> names; // ← java.util.List
java.awt.List awtList; // ← voll qualifiziert
}
Seit Java 25 kennt die Java Language Specification fünf Arten von Import-Deklarationen: die vier aus diesem Kapitel und die „single-module-import declaration“, um die es im Rest dieses Artikels geht.
Module Import Declarations
Mit import module können wir seit Java 25 (und in Java 23 und Java 24 im Preview-Modus) auch ganze Module importieren. Dadurch können wir alle Klassen, die sich innerhalb eines Moduls befinden und von diesem exportiert werden, direkt verwenden.
Die allgemeine Form lautet:
import module <Modulname>;
Im folgenden Beispiel importieren wir das Modul java.base und können dadurch die Klassen List, Map, Stream und Collectors verwenden, ohne sie einzeln oder paketweise importieren zu müssen (String und Character liegen im Paket java.lang und wurden daher schon immer automatisch importiert).
import module java.base;
public class ModuleImportTest {
public static Map<Character, List<String>> groupByFirstLetter(String... values) {
return Stream.of(values).collect(
Collectors.groupingBy(s -> Character.toUpperCase(s.charAt(0))));
}
}
Wenn du dieses Code-Beispiel in der Datei ModuleImportTest.java speicherst, kannst du die Klasse seit Java 25 wie folgt kompilieren (in Java 23/24 war import module ein Preview-Feature und musste mit --enable-preview --source <Version> aktiviert werden):
javac ModuleImportTest.java
Um import module zu verwenden, ist es – wie im vorangegangenen Beispiel gesehen – nicht nötig, dass sich die importierende Klasse selbst in einem Modul befindet.
Nur benannte Module: Classpath und Module Path
Das importierte Modul muss ein benanntes Modul sein, und es muss im sogenannten Modulgraphen stehen. Das ist die Menge der Module, die der Compiler beim Übersetzen kennt. Er baut sie aus einigen Wurzelmodulen und allem, was diese per requires benötigen. Für Code auf dem Classpath sind fast alle Module des JDK Wurzeln – sie stehen also immer im Graphen.
Eine Bibliothek auf dem Classpath gehört dagegen zum unbenannten Modul (unnamed module) und lässt sich nicht per import module importieren. javac meldet dann „imported module not found“.
Liegt die Bibliothek als Modul auf dem Module Path, steht sie damit noch nicht im Modulgraphen. Der Compiler nimmt ein Modul vom Module Path nur auf, wenn ein Modul im Graphen es per requires benötigt. Fehlt es, meldet javac „unnamed module does not read“, gefolgt vom Modulnamen. Für Code auf dem Classpath machst du so ein Modul mit --add-modules selbst zur Wurzel:
javac --module-path <Pfad zur JAR> --add-modules <Modulname> App.java
Ist dein eigener Code ein Modul, genügt stattdessen ein requires <Modulname>; in seiner module-info.java.
Automatischer java.base-Import in Compact Source Files
Compact Source Files (zuvor „Unnamed Classes“, „Implicitly Declared Classes“ und „Simple Source Files“) importieren automatisch das java.base-Modul, d. h. Klassen wie List und Map kannst du dort ohne explizite Imports verwenden.
Mehrdeutige Klassennamen
Manchmal kommt es vor, dass ein Klassenname nicht eindeutig ist. In folgendem Beispiel gibt es eine List-Klasse sowohl im importierten Modul java.base (java.util.List) als auch im Modul java.desktop (java.awt.List):
import module java.base; // ← enthält java.util.List
import module java.desktop; // ← enthält java.awt.List
public class Ambiguous {
List list; // ← mehrdeutige Referenz auf List
}
Wenn du die Datei unter Ambiguous.java abspeicherst und dann wie folgt kompilierst:
javac Ambiguous.java
... dann bricht der Compiler mit folgender Fehlermeldung ab:
Ambiguous.java:5: error: reference to List is ambiguous
List list; // ← mehrdeutige Referenz auf List
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error
Das bedeutet, dass der Compiler nicht weiß, welcher der beiden List-Typen gemeint ist.
Mehrdeutige Klassennamen auflösen
Nehmen wir an, du möchtest java.util.List verwenden (und nicht java.awt.List). Dann hast du zwei Möglichkeiten, um diese Mehrdeutigkeit aufzulösen:
Option 1: Du importierst zusätzlich die Klasse java.util.List direkt:
import module java.base;
import module java.desktop;
import java.util.List; // ← per Single-Type-Import aufgelöst
public class Ambiguous {
List list;
}
Option 2: Du importierst zusätzlich das Paket java.util (in der Java-Terminologie heißt das übrigens nicht „Package Import“, sondern „Type-Import-on-Demand Declaration“):
import module java.base;
import module java.desktop;
import java.util.*; // ← per Type-Import-on-Demand aufgelöst
public class Ambiguous {
List list;
}
Die zweite Option ist erst seit Java 24 verfügbar. Wenn du das letzte Beispiel mit Java 23 kompilierst, führt dies ebenfalls zur Fehlermeldung „reference to List is ambiguous“.
Hinter beiden Optionen steht dieselbe Regel: Der spezifischere Import hat Vorrang vor dem allgemeineren.
| Import-Art | Beispiel | hat Vorrang vor |
|---|---|---|
| Single-Type-Import | import java.util.List; | Paket- und Modul-Importen |
| Type-Import-on-Demand | import java.util.*; | Modul-Importen (seit Java 24) |
| Module Import | import module java.base; | keinem anderen Import |
Transitive Modul-Importe
Wenn ein mit import module importiertes Modul ein anderes Modul mit requires transitive benötigt, dann sind auch alle Klassen der exportierten Pakete dieses anderen Moduls ohne explizite Imports nutzbar.
Ich möchte dir das am Beispiel der Module java.sql und java.xml erklären.
- Das Modul
java.sqlhat (u. a.) eine transitive Abhängigkeit auf das Moduljava.xml. - Das Modul
java.sqlexportiert die Paketejava.sqlundjavax.sql. - Das Modul
java.xmlexportiert u. a. die Paketejavax.xml,javax.xml.parsersundorg.w3c.dom.
Die folgende Grafik zeigt die Module, ihre Abhängigkeiten und die exportierten Pakete:
In der Modul-Deklaration des java.sql-Moduls sieht das so aus:
module java.sql {
. . .
requires transitive java.xml;
exports java.sql;
exports javax.sql;
. . .
}
Und in der Modul-Deklaration von java.xml so:
module java.xml {
exports javax.xml;
exports javax.xml.parsers;
. . .
}
Wenn wir nun ein Programm schreiben, das das Modul java.sql importiert, dann benötigen wir beispielsweise keine expliziten Imports für die Klassen SAXParserFactory und SAXParser aus dem Paket javax.xml.parsers des java.xml-Moduls – und auch keinen expliziten Import dieses Moduls:
import module java.sql; // ← importiert auch die Pakete von java.xml,
// etwa javax.xml.parsers
. . .
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
. . .
Denn aus der transitiven Abhängigkeit von Modul java.sql auf Modul java.xml und der Tatsache, dass java.xml das Paket javax.xml.parsers exportiert, folgt, dass das Programm auch ohne explizite Imports auf alle Klassen des Pakets javax.xml.parsers zugreifen kann.
Automatischer java.base-Import in JShell
Seit Java 25 importiert JShell das Modul java.base automatisch:
$ jshell
| Welcome to JShell -- Version 27
| For an introduction type: /help intro
jshell> /imports
| import java.base
Wenn du JShell mit Java 24 oder älter startest und das /imports-Kommando eingibst, wirst du hingegen die folgenden zehn standardmäßigen Paket-Importe sehen:
$ jshell
| Welcome to JShell -- Version 24
| For an introduction type: /help intro
jshell> /imports
| import java.io.*
| import java.math.*
| import java.net.*
| import java.nio.file.*
| import java.util.*
| import java.util.concurrent.*
| import java.util.function.*
| import java.util.prefs.*
| import java.util.regex.*
| import java.util.stream.*
Fazit
Ein Import macht Klassen und statische Member unter ihrem einfachen Namen verfügbar – einzeln, paketweise und seit Java 25 auch modulweise. Zur Laufzeit ändert er nichts.
Module Import Declarations können Java-Programme kürzer und leichter wartbar machen, indem nicht mehr einzelne Klassen und Pakete, sondern ganze Module importiert werden. Andererseits können sie auch die ewige Diskussion darüber, ob man Klassen einzeln oder paketweise importieren soll, weiter ausufern lassen.
In Compact Source Files (zuvor Unnamed Classes, Implicitly Declared Classes und Simple Source Files) und JShell wird automatisch das java.base-Modul importiert, sodass hier alle Klassen dieses Moduls ohne Imports direkt verwendet werden können.
Ich empfehle import module dort, wo du ausprobierst: in JShell, in Compact Source Files und beim Prototyping. In großen Codebasen bleiben Single-Type-Imports die klarere Wahl, denn mit ihnen zeigt jede Datei, aus welchem Paket eine Klasse stammt – und eine neue Klasse in einem importierten Modul kann einen bisher eindeutigen Namen nicht mehrdeutig machen.
Module Import Declarations waren bis Java 24 ein Preview-Feature und mussten mit --enable-preview --source <Version> aktiviert werden. Seit Java 25 sind sie finalisiert und fester Bestandteil der Sprache – ohne zusätzliche Flags nutzbar.
Was soll ich als Nächstes vertiefen? Das erfahre ich am besten durch dein Feedback: Eine Bewertung auf meinem ProvenExpert-Profil zeigt mir, welche Themen dir wichtig sind – und motiviert mich, weitere Artikel zu schreiben.




