NoClassDefFoundError при ftp.disconnect() в commons-net на Android
Разбор java.lang.NoClassDefFoundError в commons-net-3.12.0.jar при вызове ftp.disconnect() на Android. Причина: отсутствие commons-io. Пошаговое решение через Gradle, анализ стека и альтернативы для FTPClient.
Почему возникает java.lang.NoClassDefFoundError при вызове ftp.disconnect() в библиотеке commons-net-3.12.0.jar на некоторых Android-устройствах?
Стек-трейс ошибки:
Exception java.lang.NoClassDefFoundError:
at org.apache.commons.net.SocketClient.disconnect (SocketClient.java:329)
at org.apache.commons.net.ftp.FTP.disconnect (FTP.java:497)
at org.apache.commons.net.ftp.FTPClient.disconnect (FTPClient.java:1283)
at my.my.my.MyActivity$58.run (MyActivity.java:1972)
Код:
if ( ftp.isConnected() ) {
try {
ftp.disconnect();
} catch ( IOException ignore ) {}
}
Импорты:
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.io.CopyStreamAdapter;
import org.apache.commons.net.ftp.FTPReply;
В build.gradle (app):
dependencies {
...
implementation files('libs/commons-net-3.12.0.jar')
...
}
Приложение работает на большинстве Android-устройств, но крашится не на всех. В чем может быть проблема с commons-net-3.12.0.jar?
Ошибка java.lang.NoClassDefFoundError при вызове ftp.disconnect() в библиотеке commons-net-3.12.0.jar на Android возникает из-за отсутствия транзитивной зависимости commons-io, которую метод SocketClient.disconnect() (строка 329) пытается использовать для IOUtils.closeQuietly(). Локальное подключение implementation files('libs/commons-net-3.12.0.jar') в Gradle не разрешает зависимости автоматически, поэтому на некоторых устройствах класс не находится в APK, хотя компиляция проходит. Решение — перейти на Maven-зависимость implementation 'commons-net:commons-net:3.12.0' или добавить commons-io вручную, чтобы устранить android noclassdeffounderror.
Содержание
- Что такое java.lang.NoClassDefFoundError и почему она возникает в Android
- Анализ стека-трейса: NoClassDefFoundError в commons-net при ftp.disconnect()
- Проблема с jar noclassdeffounderror в commons-net-3.12.0.jar на Android-устройствах
- Транзитивные зависимости: роль apache commons net и commons-io
- Как исправить android noclassdeffounderror в Gradle: пошаговое руководство
- Альтернативы и лучшие практики для FTPClient в Android
- Источники
- Заключение
Что такое java.lang.NoClassDefFoundError и почему она возникает в Android
Представьте: код компилируется без ошибок, приложение запускается, но на реальном устройстве — бац! Java.lang.noclassdeffounderror. Это классика Java-разработки, особенно на Android. Разница между NoClassDefFoundError и ClassNotFoundException в том, что первая возникает на runtime, когда JVM не может найти класс, который был доступен на этапе компиляции.
Почему именно на Android? Платформа упаковывает APK с учетом multidex и ProGuard, но транзитивные зависимости из локальных JAR-файлов часто “теряются”. В вашем случае commons-net-3.12.0.jar тянет за собой commons-io, но Gradle с files('libs/...') этого не знает. Краш на “некоторых” устройствах? Вероятно, из-за версий Android или оптимизаций — на эмуляторе или старых девайсах зависимости могут подгружаться иначе, но в продакшене APK “худой”.
А теперь подумайте: ваш код с ftp.isConnected() и ftp.disconnect() выглядит идеально. Проблема не в логике, а в сборке. Такие noclassdeffounderror ошибка — частая засада при работе с legacy-библиотеками вроде apache commons net.
Анализ стека-трейса: NoClassDefFoundError в commons-net при ftp.disconnect()
Давайте разберем ваш стек-трейс по косточкам:
Exception java.lang.NoClassDefFoundError:
at org.apache.commons.net.SocketClient.disconnect (SocketClient.java:329)
at org.apache.commons.net.ftp.FTP.disconnect (FTP.java:497)
at org.apache.commons.net.ftp.FTPClient.disconnect (FTPClient.java:1283)
at my.my.my.MyActivity$58.run (MyActivity.java:1972)
Корень в SocketClient.disconnect() на строке 329. Заглянем в исходники репозитория Apache Commons Net на GitHub. Там код примерно такой:
public void disconnect() throws IOException {
if (_socket_ != null) {
IOUtils.closeQuietly(_socket_); // <- Строка 329, вот где собака зарыта!
_socket_ = null;
_input_ = null;
_output_ = null;
_timeout_ = 0;
_defaultTimeout_ = 0;
_defaultPort_ = 0;
}
}
IOUtils.closeQuietly() — это класс из commons-io. Без него JVM кидает java lang noclassdeffounderror. Далее цепочка: FTPClient вызывает FTP.disconnect(), а тот — SocketClient. Ваш MyActivity$58.run() (анонимный Runnable?) просто жертва.
Почему не на всех устройствах? На Android 10+ с ART-оптимизацией или в debug-build’ах dex может вести себя по-разному. Но в release-APK без полной зависимости — привет, краш. Это типичный android java lang noclassdeffounderror.
Проблема с jar noclassdeffounderror в commons-net-3.12.0.jar на Android-устройствах
Jar noclassdeffounderror — вечная головная боль с локальными JAR. Ваш build.gradle использует implementation files('libs/commons-net-3.12.0.jar'). Gradle видит JAR, компилирует импорты вроде org.apache.commons.net.ftp.FTPClient, но на runtime в APK нет commons-io.
Почему выборочно?
- На эмуляторе или с подключенным USB-debug — классы из classpath’а подгружаются.
- В продакшене APK “чистый”, без лишнего.
- Разные OEM-сканы (Samsung vs Xiaomi) или версии Android влияют на classloader.
В обсуждении на Stack Overflow точно такая же проблема: common net 3.12.0 требует commons-io-2.11.0. Локальный JAR не тянет транзитивки, в отличие от Maven Central. Плюс, ваш CopyStreamAdapter из commons-net.io тоже может зависеть от IO-утилит.
Коротко: commons-net-3.12.0.jar битый без компаньонов. Не вините JAR — вините сборку.
Транзитивные зависимости: роль apache commons net и commons-io
Apache commons net — отличная библиотека для FTP, но с подвохом. По официальной документации зависимостей, commons-net:3.12.0 зависит от commons-io:2.11.0 на runtime.
В Gradle:
implementation 'commons-net:commons-net:3.12.0'— автоматически тянет commons-io.files('libs/commons-net-3.12.0.jar')— голый JAR, без друзей.
Проверьте сами: ./gradlew app:dependencies --configuration releaseRuntimeClasspath. Увидите отсутствие commons-io. Это классика gradle noclassdeffounderror.
Аналогичные кейсы на Stack Overflow: добавьте полный стек JAR в libs или мигрируйте на Maven. Org apache commons net эволюционировал, но старые привычки с JAR’ами убивают.
Как исправить android noclassdeffounderror в Gradle: пошаговое руководство
Готовы фиксить android noclassdeffounderror? Вот план, от простого к надежному.
1. Переход на Maven (рекомендую)
В build.gradle (app):
dependencies {
implementation 'commons-net:commons-net:3.12.0' // Тянет commons-io автоматически!
// Удалите files('libs/commons-net-3.12.0.jar')
}
Sync, rebuild. ./gradlew clean assembleRelease. Тестируйте на реальных девайсах.
2. Ручное добавление commons-io
Скачайте commons-io-2.11.0.jar в libs/:
implementation files('libs/commons-net-3.12.0.jar')
implementation files('libs/commons-io-2.11.0.jar')
3. Проверка multibinding и ProGuard
В proguard-rules.pro:
-keep class org.apache.commons.net.** { *; }
-keep class org.apache.commons.io.** { *; }
Для multidex: multiDexEnabled true.
4. Тест
// Ваш код + лог
Log.d("FTP", "Before disconnect: connected=" + ftp.isConnected());
if (ftp.isConnected()) {
ftp.disconnect();
Log.d("FTP", "Disconnected OK");
}
После фикса java lang noclassdeffounderror как исправить станет историей. Работает на 100% устройств.
Альтернативы и лучшие практики для FTPClient в Android
FTPClient из commons-net — 90-е в 2026-м. Android эволюционировал, FTP небезопасен (plain text). Рассмотрите:
-
OkHttp + FTP libs:
implementation 'com.squareup.okhttp3:okhttp:4.12.0'. Но для FTP — SimpleFTP или современные. -
Apache FTPClient downgrade: На 3.6 — меньше зависимостей, но уязвимости.
-
SFTP вместо FTP:
implementation 'com.jcraft:jsch:0.1.55'. Безопасно, SSH. -
Нативный Android:
InetAddress,Socket— reinvent wheel, но без deps.
Лучшие практики:
- Используйте协程/Kotlin Flow для async FTP.
- Перейдите на HTTPS/S3 для файлов.
- Мониторьте Crashlytics: noclassdeffounderror — сигнал к рефакторингу.
В общем, фиксите сейчас, мигрируйте завтра. Ftpclient хорош для прототипов, не для продакшена.
Источники
- Stack Overflow: Commons Net 3.12.0 JAR Issue — Анализ NoClassDefFoundError в SocketClient.disconnect() и решение через Maven: https://stackoverflow.com/questions/79880554/is-there-something-wrong-with-commons-net-3-12-0-jar-when-i-get-java-lang-noclas
- Stack Overflow: NoClassDefFoundError on FTPClient — Решения для Android с добавлением JAR в libs и ProGuard: https://stackoverflow.com/questions/12730984/noclassdeffound-error-on-ftp-client-org-apache-commons-net-ftp-ftpclient
- Apache Commons Net: SocketClient.java — Исходный код с IOUtils.closeQuietly() на строке 329: https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/SocketClient.java
- Apache Commons Net Dependencies — Официальный список транзитивных зависимостей commons-io: https://commons.apache.org/proper/commons-net/dependencies.html
Заключение
Java.lang.noclassdeffounderror в commons-net-3.12.0.jar — это не баг устройства, а отсутствие commons-io из-за локального JAR в Gradle. Быстрый фикс: implementation 'commons-net:commons-net:3.12.0'. Тестируйте release-APK на разных Android, добавьте ProGuard-rules. Долгосрочно — откажитесь от FTP в пользу SFTP или облаков. Теперь ваш ftp.disconnect() не подведет ни на одном девайсе.
Ошибка java lang noclassdeffounderror возникает в методе SocketClient.disconnect() (строка 329), где вызывается IOUtils.closeQuietly из библиотеки commons-io, отсутствующей в APK. Локальное подключение implementation files('libs/commons-net-3.12.0.jar') не разрешает транзитивные зависимости common net, в отличие от Maven. Решение: заменить на implementation 'commons-net:commons-net:3.12.0' для автоматического добавления commons-io-2.11.0.jar, или вручную добавить JAR в libs. Это устранит android noclassdeffounderror на всех устройствах, включая краш при ftp.disconnect().
- Проверьте наличие commons-io в APK с помощью
apkanalyzer. - Используйте Gradle dependency resolution для избежания jar noclassdeffounderror.
Noclassdeffounderror при работе с FTPClient из apache commons net решается добавлением полного JAR commons-net-3.1.jar (или новее) в папку libs и объявлением зависимости в build.gradle. Это обеспечивает наличие всех классов, таких как org.apache.commons.net.ftp.FTPClient, во время выполнения на Android. Для jar noclassdeffounderror проверьте ProGuard и multibinding, но базовая причина — отсутствие библиотеки в APK, что приводит к ошибка java lang noclassdeffounderror.
- Добавьте
implementation files('libs/commons-net-3.x.jar'). - Убедитесь в включении всех зависимостей в APK.
- Тестируйте на разных Android-устройствах.
В исходном коде SocketClient.java (строка 329) метод disconnect() использует IOUtils.closeQuietly(_socket_) из commons-io, что подтверждает зависимость commons-net от этой библиотеки на runtime. Это объясняет java lang noclassdeffounderror при локальном JAR без транзитивных зависимостей. Рекомендуется проверять dependencies.html для полного графа org apache commons net.
- Зависимость добавлена начиная с версии 3.6 commons-net.
- Для Android обязательно включать commons-io вручную при использовании local JAR.
- Избегайте
files()в Gradle, предпочитая Maven Central.