No third-party dependencies, guards against zip slip, fully commented, recreates directory structure recursively, ignores empty directories, sane source code nesting, extracts to zip file's directory, and uses UTF-8. Usage:
Path zipFile = Path.of( "/path/to/filename.zip" );
Zip.extract( zipFile );
Here's the code:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
/**
 * Responsible for managing zipped archive files.
 */
public final class Zip {
  /**
   * Extracts the contents of the zip archive into its current directory. The
   * contents of the archive must be {@link StandardCharsets#UTF_8}. For
   * example, if the {@link Path} is <code>/tmp/filename.zip</code>, then
   * the contents of the file will be extracted into <code>/tmp</code>.
   *
   * @param zipPath The {@link Path} to the zip file to extract.
   * @throws IOException Could not extract the zip file, zip entries, or find
   *                     the parent directory that contains the path to the
   *                     zip archive.
   */
  public static void extract( final Path zipPath ) throws IOException {
    assert !zipPath.toFile().isDirectory();
    try( final var zipFile = new ZipFile( zipPath.toFile() ) ) {
      iterate( zipFile );
    }
  }
  /**
   * Extracts each entry in the zip archive file.
   *
   * @param zipFile The archive to extract.
   * @throws IOException Could not extract the zip file entry.
   */
  private static void iterate( final ZipFile zipFile )
    throws IOException {
    // Determine the directory name where the zip archive resides. Files will
    // be extracted relative to that directory.
    final var path = getDirectory( zipFile );
    final var entries = zipFile.entries();
    while( entries.hasMoreElements() ) {
      final var zipEntry = entries.nextElement();
      final var zipEntryPath = path.resolve( zipEntry.getName() );
      // Guard against zip slip.
      if( zipEntryPath.normalize().startsWith( path ) ) {
        extract( zipFile, zipEntry, zipEntryPath );
      }
    }
  }
  /**
   * Extracts a single entry of a zip file to a given directory. This will
   * create the necessary directory path if it doesn't exist. Empty
   * directories are not re-created.
   *
   * @param zipFile      The zip archive to extract.
   * @param zipEntry     An entry in the zip archive.
   * @param zipEntryPath The file location to write the zip entry.
   * @throws IOException Could not extract the zip file entry.
   */
  private static void extract(
    final ZipFile zipFile,
    final ZipEntry zipEntry,
    final Path zipEntryPath ) throws IOException {
    // Only attempt to extract files, skipping empty directories.
    if( !zipEntry.isDirectory() ) {
      createDirectories( zipEntryPath.getParent() );
      try( final var in = zipFile.getInputStream( zipEntry ) ) {
        Files.copy( in, zipEntryPath, REPLACE_EXISTING );
      }
    }
  }
  /**
   * Helper method to return the normalized directory where the given archive
   * resides.
   *
   * @param zipFile The {@link ZipFile} having a path to normalize.
   * @return The directory containing the given {@link ZipFile}.
   * @throws IOException The zip file has no parent directory.
   */
  private static Path getDirectory( final ZipFile zipFile ) throws IOException {
    final var zipPath = Path.of( zipFile.getName() );
    final var parent = zipPath.getParent();
    if( parent == null ) {
      throw new IOException( zipFile.getName() + " has no parent directory." );
    }
    return parent.normalize();
  }
}
Now that you have the core algorithm in place, you need to check the file extension for ".zip" and, if present, recursively call Zip.extract( ... ) on that file.