Где я? Или как не заблудиться в файловой системе Java-приложения
Каждый Java-разработчик хотя бы раз в жизни задавался вопросом: «А где, собственно, выполняется мой код?» Вроде бы запускаешь программу из IDE, всё работает. Но стоит перенести её на сервер или в Docker-контейнер — и файлы, которые должны были лежать рядом с jar-файлом, оказываются где-то в недрах /opt/app или /tmp. Знакомая ситуация?
Вопрос «How to get the current working directory in Java?» — один из самых популярных на Stack Overflow. И неспроста: ответ кажется очевидным, но на практике таит множество подводных камней. В этой статье мы разберём лучшие практики 2025 года: как надёжно получить current working directory, когда можно ему доверять, а когда лучше искать альтернативы.
Дисклеймер: статья написана для Java 17+ (LTS), но примеры совместимы с Java 11 и новее. Если вы всё ещё на Java 8 — срочно обновляйтесь, но код тоже будет работать.
Классический способ: System.getProperty("user.dir")
Самый простой и широко используемый метод — обратиться к системному свойству user.dir. Это то, что вы увидите в 90% ответов на Stack Overflow:
public class WorkingDirectoryExample {
public static void main(String[] args) {
String cwd = System.getProperty("user.dir");
System.out.println("Текущая директория: " + cwd);
}
}
Что вы получите на выходе? Путь к папке, из которой была запущена JVM. Если запускаете из корня проекта — это будет /Users/you/projects/my-app. Если из target/ — будет target/.
Почему это не всегда надёжно?
Проблема №1: IDE и билд-системы обманывают. IntelliJ IDEA по умолчанию устанавливает working directory равным корню модуля. Maven — корню проекта. Gradle — корню модуля. Но это можно переопределить в конфигурации запуска. В результате на локальной машине код работает, а на сервере — нет.
Проблема №2: Unit-тесты. При запуске через JUnit или TestNG working directory может быть корнем проекта, а может — папкой билда. Зависит от настроек раннера.
Проблема №3: Веб-приложения и микросервисы. В контейнерах (Tomcat, Jetty) working directory часто равен $CATALINA_HOME/bin или /. Никакого отношения к вашему WAR-файлу это не имеет.
Современный подход: java.nio.file.Paths
Начиная с Java 7, у нас есть более «джавовый» способ — через NIO.2 API:
import java.nio.file.Paths;
public class NioWorkingDirectory {
public static void main(String[] args) {
String cwd = Paths.get("").toAbsolutePath().toString();
System.out.println("Текущая директория (NIO): " + cwd);
}
}
Что здесь происходит? Paths.get("") создаёт ссылку на пустой путь, который интерпретируется как относительный путь к текущей директории. Метод toAbsolutePath() разрешает его в абсолютный. Результат — тот же user.dir, но с более строгой типизацией.
Плюсы:
- Работает с объектами
Path, а не со строками (меньше ошибок с разделителями) - Можно сразу использовать в современных API
- Кроссплатформенность (Windows vs Linux)
Минусы: всё те же — это по-прежнему working directory процесса.
Альтернатива: File("")
Для тех, кто не хочет переходить на NIO (хотя зря):
import java.io.File;
public class FileWorkingDirectory {
public static void main(String[] args) {
String cwd = new File("").getAbsolutePath();
System.out.println("Текущая директория (File): " + cwd);
}
}
Абсолютно то же самое, что и Paths.get("").toAbsolutePath(), но с использованием старого java.io.File. Работает, но не даёт преимуществ NIO.
Когда working directory — зло? Разбор реальных кейсов
Кейс 1: Чтение конфигурационного файла
Допустим, у вас есть config.properties в корне проекта. Вы пишете:
// ПЛОХОЙ ПРИМЕР
File configFile = new File("config.properties");
Это сработает только если JVM запущена из корня проекта. В production окружении файл не найдётся. Правильное решение — класть конфиги в classpath:
// ХОРОШИЙ ПРИМЕР
InputStream input = getClass().getClassLoader()
.getResourceAsStream("config.properties");
Кейс 2: Создание временных файлов
Никогда не создавайте временные файлы в working directory! Используйте System.getProperty("java.io.tmpdir") или Files.createTempFile():
Path tempFile = Files.createTempFile("myapp-", ".tmp");
System.out.println("Временный файл: " + tempFile);
Кейс 3: Логирование путей
Если вам нужно залогировать, откуда запущено приложение — user.dir подойдёт. Но не используйте его для бизнес-логики.
Лучшие практики на 2025 год
После тысяч прочитанных issue на GitHub и багов в продакшене я выработал несколько железных правил:
- Не полагайтесь на working directory. Если вам нужен файл, который лежит рядом с jar — используйте
ProtectionDomainилиgetCodeSource(). - Используйте Paths.get("") только для отладки. В продакшене — явно передавайте пути через конфигурацию или переменные окружения.
- Для веб-приложений — получайте реальный путь через
ServletContext.getRealPath(). - Для unit-тестов — не используйте working directory. Используйте временные папки через
@TempDir(JUnit 5).
Пример: как правильно получить путь к jar-файлу
import java.net.URL;
import java.security.ProtectionDomain;
public class JarLocation {
public static void main(String[] args) {
ProtectionDomain pd = JarLocation.class.getProtectionDomain();
URL location = pd.getCodeSource().getLocation();
System.out.println("JAR находится здесь: " + location);
}
}
Этот код покажет, откуда физически загружен ваш класс. Если вы запускаете из IDE — это будет папка target/classes. Если из jar — путь к jar-фай