mybatis/mybatis-3 · error · IOException
Bad zip entry: {}
Error message
Bad zip entry: {} What it means
DefaultVFS (used to scan for mapper classes/packages on the classpath) streams a jar resource and, for each entry, checks that the resolved canonical path stays inside the destination directory. An entry whose path escapes it (../ traversal, absolute path) throws 'Bad zip entry' — this is zip-slip protection against maliciously crafted jars, and it can also fire on unusual jar entry names.
Source
Thrown at src/main/java/org/apache/ibatis/io/DefaultVFS.java:89
} else {
List<String> children = new ArrayList<>();
try {
if (isJar(url)) {
// Some versions of JBoss VFS might give a JAR stream even if the resource
// referenced by the URL isn't actually a JAR
is = url.openStream();
try (JarInputStream jarInput = new JarInputStream(is)) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
Path destinationDir = Path.of(path);
for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) {
if (log.isDebugEnabled()) {
log.debug("Jar entry: " + entry.getName());
}
File entryFile = destinationDir.resolve(entry.getName()).toFile().getCanonicalFile();
if (!entryFile.getPath().startsWith(destinationDir.toFile().getCanonicalPath())) {
throw new IOException("Bad zip entry: " + entry.getName());
}
children.add(entry.getName());
}
}
} else {
/*
* Some servlet containers allow reading from directory resources like a text file, listing the child
* resources one per line. However, there is no way to differentiate between directory and file resources
* just by reading them. To work around that, as each line is read, try to look it up via the class loader
* as a child of the current resource. If any line fails then we assume the current resource is not a
* directory.
*/
is = url.openStream();
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
for (String line; (line = reader.readLine()) != null;) {
if (log.isDebugEnabled()) {
log.debug("Reader entry: " + line);View on GitHub (pinned to 008069adb1)
Solutions
- Update MyBatis to the latest 3.5.x — VFS handling of nested/spring-boot jars has been improved repeatedly.
- Register a custom VFS (e.g. SpringBootVFS via configuration.setVfsImpl(...)) instead of DefaultVFS when running from an executable jar.
- Narrow the scanned packages so the offending jar is not scanned (avoid scanning classpath roots like classpath*:**).
- If a specific jar is malformed, identify it from the logged 'Listing <url>' debug line and exclude/replace it.
Example fix
// before (spring boot fat jar, DefaultVFS scanning fails)
sqlSessionFactoryBean.setTypeAliasesPackage("com.example.domain");
// after
sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
sqlSessionFactoryBean.setTypeAliasesPackage("com.example.domain"); Defensive patterns
Strategy: fallback
Validate before calling
// detect fat-jar protocol and pick a suitable VFS before session creation
URL u = Thread.currentThread().getContextClassLoader().getResource("mappers");
if (u != null && ("jar".equals(u.getProtocol()) || u.toExternalForm().contains("nested"))) {
targetConfiguration.setVfsImpl(org.mybatis.spring.boot.autoconfigure.SpringBootVFS.class);
} Prevention
- In Spring Boot executables, always set VFS to SpringBootVFS (mybatis-spring-boot-starter does this for you — use the starter).
- Keep mybatis current; VFS jar handling improves across 3.5.x releases.
- Scope package scanning tightly to your own mapper packages instead of broad roots.
When it happens
Trigger: MyBatis VFS scans a package for mapper candidates (e.g. <package name="..."/> or typeAliasesPackage/typeHandlersPackage scanning) and encounters a jar (including nested jars in fat jars / spring-boot executable jars) containing entries like '../../x' or '/etc/passwd' whose canonical resolution leaves the destination dir.
Common situations: Spring Boot fat jars where nested-jar URL protocols interact badly with Path.of()/canonicalization; third-party jars containing unusual entry names; containers/SCM jars; security scanners injecting crafted jars; older mybatis versions against modern packaging.
Related errors
- Error setting driver on UnpooledDataSource.
- Cannot enable lazy loading because CGLIB is not available. A
- Cannot enable lazy loading because Javassist is not availabl
- Unknown statement type: {}
- Cannot find class: {}
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/c1bdd564d0f36521.
Report an issue: GitHub.