Как победить "Xerces Hell" в Java/Maven проектах?
Что такое "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.