使用Apache Tika提取元数据时偶发ArrayIndexOutOfBoundsException问题求助
问题
使用Apache Tika提取文本、PDF文件内容时,调用metadata.names()方法偶发ArrayIndexOutOfBoundsException异常,出现概率约2-3%。
相关代码
public ExtractionResponse getText(InputStream sourceInputStream, int maxExtractedStringLength) { tika.setMaxStringLength(maxExtractedStringLength); Metadata metadata = new Metadata(); try { Reader extractedContentReader = tika.parse(sourceInputStream, metadata); ReaderInputStream readerInputStream = ReaderInputStream.builder().setReader(extractedContentReader).get(); return ExtractionResponse.of(readerInputStream, getMetadatas(metadata), StringUtils.EMPTY); } catch (IOException e) { log.info("Error in file extraction", e); throw new ApplicationRuntimeException(e); } } private String getMetadatas(Metadata metadata) throws IOException { Map<String, List<String>> metadataMap = Stream.of(metadata.names()) .collect(Collectors.toMap(Function.identity(), name -> asList(metadata.getValues(name)))); return JSONUtil.generateJSON(metadataMap); }
异常栈信息
java.lang.ArrayIndexOutOfBoundsException: Index 14 out of bounds for length 14 at java.base/java.util.HashMap.keysToArray(HashMap.java:950) at java.base/java.util.HashMap$KeySet.toArray(HashMap.java:997) at org.apache.tika.metadata.Metadata.names(Metadata.java:156) at com.myzone.extractors.TikaDocumentHandler.getMetadatas(TikaDocumentHandler.java:81) at com.myzone.extractors.TikaDocumentHandler.getText(TikaDocumentHandler.java:54)
排查与解决
原因分析
从异常栈可以定位到问题出在HashMap.keysToArray方法,核心原因是Tika的Metadata内部依赖的HashMap存在并发修改:
- 你的代码看似单线程调用,但Tika解析过程中可能启动异步线程修改Metadata;
- 若多线程环境下共享了同一个Tika实例,会导致Metadata被并发读写;
- 旧版本Tika(如1.x系列部分版本)的Metadata类未做线程安全处理,内部HashMap在并发场景下会触发数组越界这类并发异常。
解决方案
- 保证Tika实例的线程隔离
Tika类本身不是线程安全的,禁止多线程共享同一个实例。可以用ThreadLocal维护每个线程的独立实例:
private ThreadLocal<Tika> tikaThreadLocal = ThreadLocal.withInitial(Tika::new); public ExtractionResponse getText(InputStream sourceInputStream, int maxExtractedStringLength) { Tika tika = tikaThreadLocal.get(); tika.setMaxStringLength(maxExtractedStringLength); // 后续解析逻辑不变 }
升级Tika版本至2.x系列
Tika 2.x已重构Metadata实现,改用线程安全的数据结构,彻底解决了这类并发问题。如果当前使用1.x版本,直接升级到最新稳定版是最优解。临时同步Metadata访问
若暂时无法升级版本,可对Metadata的读写操作加锁,缓解并发冲突:
private String getMetadatas(Metadata metadata) throws IOException { synchronized (metadata) { Map<String, List<String>> metadataMap = Stream.of(metadata.names()) .collect(Collectors.toMap(Function.identity(), name -> asList(metadata.getValues(name)))); return JSONUtil.generateJSON(metadataMap); } }
注意:此方案仅为临时缓解,若Tika内部异步修改Metadata,锁无法完全覆盖所有场景,优先推荐前两种方案。
内容的提问来源于stack exchange,提问作者user3212707
相关产品推荐
相关产品推荐

