命令行运行POI相关Jar报错:找不到org.apache.poi.ss.usermodel.CellStyle
我有一个从CSV生成预格式化Excel文件的Java程序,想打包成Jar在Windows主机运行。这个程序在NetBeans里运行多年都正常,但用Maven执行clean和build生成Jar后,用java -jar命令行运行时出现以下错误:
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/CellStyle
我很困惑,因为我并没有使用SS.usermodel.CellStyle,而是用的XSSF.usermodel.XSSFCellStyle接口,为什么会报SS相关的错误?我已经引入了版本均为5.0.0的poi和poi-ooxml依赖,因为我用到了仅在poi中存在的类。
补充细节
执行命令
java -jar /Users/csomabalint/Java/formatpenaltyfile/target/formatpenaltyfile-1.0-SNAPSHOT.jar
完整错误栈
Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/CellStyle at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.privateGetMethodRecursive(Class.java:3048) at java.lang.Class.getMethod0(Class.java:3018) at java.lang.Class.getMethod(Class.java:1784) at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:650) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:632) Caused by: java.lang.ClassNotFoundException: org.apache.poi.ss.usermodel.CellStyle at java.net.URLClassLoader.findClass(URLClassLoader.java:387) at java.lang.ClassLoader.loadClass(ClassLoader.java:419) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:365) at java.lang.ClassLoader.loadClass(ClassLoader.java:352) ... 7 more
NetBeans中运行命令及日志
cd /Users/csomabalint/Java/formatpenaltyfile; JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home "/Applications/NetBeans/Apache NetBeans 12.2.app/Contents/Resources/NetBeans/netbeans/java/maven/bin/mvn" "-Dexec.args=-classpath %classpath com.xxx.formatpenaltyfile.FormatterKlass" -Dexec.executable=/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/java org.codehaus.mojo:exec-maven-plugin:3.0.0:exec
Running NetBeans Compile On Save execution. Phase execution is skipped and output directories of dependency projects (with Compile on Save turned on) will be used instead of their jar artifacts. Scanning for projects... ---------------------< com.xxx:formatpenaltyfile >---------------------- Building formatpenaltyfileNew 1.0-SNAPSHOT --------------------------------[ jar ]--------------------------------- --- exec-maven-plugin:3.0.0:exec (default-cli) @ formatpenaltyfile --- BUILD SUCCESS Total time: 13.118 s Finished at: 2024-09-23T19:25:16+02:00
打包日志
cd /Users/csomabalint/Java/formatpenaltyfile; JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home "/Applications/NetBeans/Apache NetBeans 12.2.app/Contents/Resources/NetBeans/netbeans/java/maven/bin/mvn" install
--- maven-compiler-plugin:3.1:compile (default-compile) @ formatpenaltyfile --- Changes detected - recompiling the module! Compiling 2 source files to /Users/csomabalint/Java/formatpenaltyfile/target/classes --- maven-resources-plugin:2.6:testResources (default-testResources) @ formatpenaltyfile --- Using 'UTF-8' encoding to copy filtered resources. skip non existing resourceDirectory /Users/csomabalint/Java/formatpenaltyfile/src/test/resources --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ formatpenaltyfile --- Nothing to compile - all classes are up to date --- maven-surefire-plugin:2.12.4:test (default-test) @ formatpenaltyfile --- No tests to run. --- maven-jar-plugin:3.2.0:jar (default-jar) @ formatpenaltyfile --- Building jar: /Users/csomabalint/Java/formatpenaltyfile/target/formatpenaltyfile-1.0-SNAPSHOT.jar --- maven-install-plugin:2.4:install (default-install) @ formatpenaltyfile --- Installing /Users/csomabalint/Java/formatpenaltyfile/target/formatpenaltyfile-1.0-SNAPSHOT.jar to /Users/csomabalint/.m2/repository/com/xxx/formatpenaltyfile/1.0-SNAPSHOT/formatpenaltyfile-1.0-SNAPSHOT.jar Installing /Users/csomabalint/Java/formatpenaltyfile/pom.xml to /Users/csomabalint/.m2/repository/com/xxx/formatpenaltyfile/1.0-SNAPSHOT/formatpenaltyfile-1.0-SNAPSHOT.pom
POM.xml内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xxx</groupId> <artifactId>formatpenaltyfile</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.0.0</version> <exclusions> <exclusion> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>com.xxx.formatpenaltyfile.FormatterKlass</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> <name>formatpenaltyfileNew</name> </project>
问题原因
XSSFCellStyle依赖父接口:XSSFCellStyle本质是实现了org.apache.poi.ss.usermodel.CellStyle接口,即便代码里直接用XSSFCellStyle,运行时依然需要这个父接口的类文件,缺失就会触发NoClassDefFoundError。- 默认Jar不包含依赖:Maven的
maven-jar-plugin生成的Jar只包含你的代码,不会打包poi等第三方依赖,运行时找不到这些类。 - NetBeans运行正常的原因:NetBeans会自动把所有依赖Jar加入类路径,所以能找到所需类。
解决步骤
方法一:生成可执行Fat Jar(推荐)
用maven-shade-plugin把所有依赖打包到一个Jar里,运行时无需额外处理类路径。修改POM.xml的<build><plugins>块,添加以下插件:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.xxx.formatpenaltyfile.FormatterKlass</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
执行mvn clean package后,target目录下会生成类似formatpenaltyfile-1.0-SNAPSHOT-shaded.jar的文件,直接用java -jar运行即可。
方法二:保留依赖类路径配置
如果不想打包成Fat Jar,可以配置Maven自动复制依赖,并在Jar的Manifest中指定类路径:
- 修改
maven-jar-plugin的配置,添加类路径前缀:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.xxx.formatpenaltyfile.FormatterKlass</mainClass> </manifest> </archive> </configuration> </plugin>
- 添加
maven-dependency-plugin自动复制依赖到target/lib:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin>
- 执行
mvn clean package后,将target目录下的Jar和lib文件夹一起复制到Windows主机,运行java -jar命令即可。
额外优化
你的POM中对poi-ooxml排除了poi依赖,这没必要——poi-ooxml本身依赖poi,且你已单独引入同版本的poi,移除这个排除可以避免潜在的依赖冲突:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.0.0</version> <!-- 移除exclusions块 --> </dependency>
内容的提问来源于stack exchange,提问作者Balint9004

