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

  1. Update MyBatis to the latest 3.5.x — VFS handling of nested/spring-boot jars has been improved repeatedly.
  2. Register a custom VFS (e.g. SpringBootVFS via configuration.setVfsImpl(...)) instead of DefaultVFS when running from an executable jar.
  3. Narrow the scanned packages so the offending jar is not scanned (avoid scanning classpath roots like classpath*:**).
  4. 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

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


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/c1bdd564d0f36521. Report an issue: GitHub.