Avatar ·

Как победить "Xerces Hell" в Java/Maven проектах?

Всем привет! Я начинающий Java разработчик и уже вторую неделю борюсь с проблемой, которую в интернете называют "Xerces Hell". У нас есть большой Maven проект (Spring Boot + Hibernate). Внезапно при запуске тестов начали падать ошибки вроде: ``` java.lang.NoSuchMethodError: org.apache.xerces.impl.dv.util.Base64.(Ljava/lang/String;)V ``` или ``` java.lang.ClassNotFoundException: org.apache.xerces.parsers.SAXParser ``` Причем в одних модулях код работает, в других — нет. Я добавил зависимость xerces в pom.xml явно, но стало только хуже. Теперь у меня в classpath появились два разных класса org.apache.xerces.jaxp.SAXParserImpl — один из библиотеки xercesImpl, другой из xml-apis. Пробовал исключать транзитивные зависимости через — частично помогло, но не полностью. В итоге приложение работает только если я вручную удаляю jar-файлы из локального репозитория Maven. Подскажите, как правильно решить эту проблему? Что такое "Xerces Hell" и как его избежать в будущем? Может, есть какой-то стандартный подход к управлению XML парсерами в Java? <<>>

Что такое "Xerces Hell"?

"Xerces Hell" — это классическая проблема конфликта версий XML-парсеров в Java. Она возникает, когда в classpath проекта попадают несколько реализаций одного и того же XML API (например, Xerces, Crimson, Oracle XML Parser) или разные версии одной библиотеки (xercesImpl 2.8.0 и 2.11.0). Java использует Service Provider Interface (SPI) для загрузки SAX/DOM парсеров, и если в classpath есть несколько кандидатов — начинаются хаотичные ошибки.

Почему это происходит?

  • Транзитивные зависимости: Maven подтягивает xercesImpl как зависимость 3-го или 4-го уровня (например, через Apache POI, Axis, XMLBeans).
  • Конфликт xml-apis и xercesImpl: Библиотека xml-apis содержит интерфейсы XML (org.w3c.dom, org.xml.sax), а xercesImpl — их реализацию. Если они разных версий — ломается совместимость.
  • Ошибка ближайшего определения: Maven выбирает "ближайшую" к корню версию, но это не гарантирует совместимость с другими библиотеками.

Решение: Полное исключение и использование встроенного парсера JDK

Начиная с Java 6, JDK включает встроенный парсер Xalan/Xerces (пакет com.sun.org.apache.xerces). Лучшая стратегия — исключить все сторонние XML-библиотеки и положиться на встроенный парсер. Вот как это сделать:

<!-- В корневом pom.xml или parent pom -->
<dependencyManagement>
    <dependencies>
        <!-- Принудительно исключаем Xerces -->
        <dependency>
            <groupId>org.apache.xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>2.12.2</version> <!-- последняя стабильная -->
            <scope>provided</scope> <!-- не включаем в финальный артефакт -->
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- В каждом модуле -->
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.12.2</version>
    <exclusions>
        <exclusion>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Но лучше — полное избавление:

<!-- Запрещаем все версии xerces и xml-apis -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>9999-not-exist</version> <!-- несуществующая версия -->
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
            <version>9999-not-exist</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Явно исключаем из всех транзитивных зависимостей -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <exclusions>
        <exclusion>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
        </exclusion>
        <exclusion>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Диагностика

Используйте mvn dependency:tree для поиска всех вхождений Xerces:

mvn dependency:tree -Dincludes=*xerces*,*xml-apis*

Важно!

  • Если ваша библиотека требует конкретную версию Xerces (например, старый Apache Axis) — обновите её до версии, поддерживающей JDK 8+.
  • Для новых проектов используйте Java XML API (javax.xml.parsers) — он гарантированно использует встроенный парсер.
  • В тестах добавьте JVM аргумент: -Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
<<>>

Альтернативный подход: Maven Enforcer + BOM

Если вы не можете полностью избавиться от Xerces (например, используете legacy библиотеки), используйте Maven Enforcer Plugin для блокировки нежелательных версий:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.4.1</version>
    <executions>
        <execution>
            <id>enforce-versions</id>
            <goals><goal>enforce</goal></goals>
            <configuration>
                <rules>
                    <bannedDependencies>
                        <excludes>
                            <exclude>xerces:xercesImpl:(-,2.12.0)</exclude>
                            <exclude>xml-apis:xml-apis:(-,1.4.01)</exclude>
                        </excludes>
                    </bannedDependencies>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

Также рекомендую использовать Maven BOM (Bill of Materials) от Spring или Apache для централизованного управления версиями. Например, spring-boot-dependencies уже включает корректные версии XML-библиотек.

В крайнем случае, можно переопределить парсер через JVM аргументы при запуске:

-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl

Но помните: это временное решение. Лучше один раз навести порядок в зависимостях, чем потом бороться с "адским" classpath.