Merge branch 'stable-2.11'

* stable-2.11:
  Update 2.11 release notes
  Fix minor bugs in ChangeHookRunner
  RebaseDialog: Organize imports
  Force javac to use -encoding UTF-8
  Fix NullPointerException when executing query with --comments option
  Remove '--recheck-mergeable' option in 2.11 release notes
  ReceiveCommits: Fix NPE when pushing to refs/changes/n
  ChangeControl: Optimize creation by not re-reading changes

Change-Id: Iab97e14b80b1867f785034544ecb33bb39aab2ef
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8f04d7c..adba1aa 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -844,6 +844,30 @@
 +
 Default is "Submit patch set ${patchSet} into ${branch}".
 
+[[change.submitWholeTopic]]change.submitWholeTopic::
++
+Determines if the submit button submits the whole topic instead of
+just the current change.
++
+Default is false.
+
+[[change.submitTopicLabel]]change.submitTopicLabel::
++
+If `change.submitWholeTopic` is set and a change has a topic,
+the label name for the submit button is given here instead of
+the configuration `change.submitLabel`.
++
+Defaults to "Submit whole topic"
+
+[[change.submitTopicTooltip]]change.submitTopicTooltip::
++
+If `change.submitWholeTopic` is configuerd to true and a change has a
+topic, this configuration determines the tooltip for the submit button
+instead of `change.submitTooltip`. The variable `${topicSize}` is available
+for the number of changes to be submitted.
++
+Defaults to "Submit all ${topicSize} changes within the topic".
+
 [[change.replyLabel]]change.replyLabel::
 +
 Label name for the reply button. In the user interface an ellipsis (…)
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index cecb258..a672b177 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -36,7 +36,7 @@
 ----
 mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
     -DarchetypeArtifactId=gerrit-plugin-archetype \
-    -DarchetypeVersion=2.10 \
+    -DarchetypeVersion=2.12-SNAPSHOT \
     -DgroupId=com.googlesource.gerrit.plugins.testplugin \
     -DartifactId=testplugin
 ----
@@ -1717,17 +1717,18 @@
 [[data-directory]]
 == Data Directory
 
-Plugins can request a data directory with a `@PluginData` File
-dependency. A data directory will be created automatically by the
-server in `$site_path/data/$plugin_name` and passed to the plugin.
+Plugins can request a data directory with a `@PluginData` Path (or File,
+deprecated) dependency. A data directory will be created automatically
+by the server in `$site_path/data/$plugin_name` and passed to the
+plugin.
 
 Plugins can use this to store any data they want.
 
 [source,java]
 ----
 @Inject
-MyType(@PluginData java.io.File myDir) {
-  new FileInputStream(new File(myDir, "my.config"));
+MyType(@PluginData java.nio.file.Path myDir) {
+  this.in = Files.newInputStream(myDir.resolve("my.config"));
 }
 ----
 
diff --git a/VERSION b/VERSION
index b05afb0..83d3d2a 100644
--- a/VERSION
+++ b/VERSION
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = '2.11-SNAPSHOT'
+GERRIT_VERSION = '2.12-SNAPSHOT'
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 8548b5c..8f4c2d4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -43,7 +43,8 @@
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.eclipse.jgit.lib.Config;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 class InMemoryTestingDatabaseModule extends LifecycleModule {
   private final Config cfg;
@@ -58,9 +59,10 @@
       .annotatedWith(GerritServerConfig.class)
       .toInstance(cfg);
 
-    bind(File.class)
+    // TODO(dborowitz): Use jimfs.
+    bind(Path.class)
       .annotatedWith(SitePath.class)
-      .toInstance(new File("UNIT_TEST_GERRIT_SITE"));
+      .toInstance(Paths.get("UNIT_TEST_GERRIT_SITE"));
 
     bind(GitRepositoryManager.class)
       .toInstance(new InMemoryRepositoryManager());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 8945a22d..88fb422 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -22,6 +22,8 @@
 
 import org.junit.Test;
 
+import java.util.List;
+
 public class AccountIT extends AbstractDaemonTest {
 
   @Test
@@ -59,4 +61,21 @@
         .unstarChange(triplet);
     assertThat(getChange(triplet).starred).isNull();
   }
+
+  @Test
+  public void suggestAccounts() throws Exception {
+    String adminUsername = "admin";
+    List<AccountInfo> result = gApi.accounts()
+        .suggestAccounts().withQuery(adminUsername).get();
+    assertThat(result.size()).is(1);
+    assertThat(result.get(0).username.equals(adminUsername));
+
+    List<AccountInfo> resultShortcutApi = gApi.accounts()
+        .suggestAccounts(adminUsername).get();
+    assertThat(resultShortcutApi.size()).is(result.size());
+
+    List<AccountInfo> emptyResult = gApi.accounts()
+        .suggestAccounts("unknown").get();
+    assertThat(emptyResult).isEmpty();
+  }
 }
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 5870f91..54a2246 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -37,7 +37,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -51,7 +53,7 @@
 
   private final DefaultCacheFactory defaultFactory;
   private final Config config;
-  private final File cacheDir;
+  private final Path cacheDir;
   private final List<H2CacheImpl<?, ?>> caches;
   private final DynamicMap<Cache<?, ?>> cacheMap;
   private final ExecutorService executor;
@@ -65,23 +67,7 @@
       DynamicMap<Cache<?, ?>> cacheMap) {
     defaultFactory = defaultCacheFactory;
     config = cfg;
-
-    File loc = site.resolve(cfg.getString("cache", null, "directory"));
-    if (loc == null) {
-      cacheDir = null;
-    } else if (loc.exists() || loc.mkdirs()) {
-      if (loc.canWrite()) {
-        log.info("Enabling disk cache " + loc.getAbsolutePath());
-        cacheDir = loc;
-      } else {
-        log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
-        cacheDir = null;
-      }
-    } else {
-      log.warn("Can't create disk cache: " + loc.getAbsolutePath());
-      cacheDir = null;
-    }
-
+    cacheDir = getCacheDir(site, cfg.getString("cache", null, "directory"));
     caches = Lists.newLinkedList();
     this.cacheMap = cacheMap;
 
@@ -103,6 +89,27 @@
     }
   }
 
+  private static Path getCacheDir(SitePaths site, String name) {
+    if (name == null) {
+      return null;
+    }
+    Path loc = site.resolve(name).toPath();
+    if (!Files.exists(loc)) {
+      try {
+        Files.createDirectories(loc);
+      } catch (IOException e) {
+        log.warn("Can't create disk cache: " + loc.toAbsolutePath());
+        return null;
+      }
+    }
+    if (!Files.isWritable(loc)) {
+      log.warn("Can't write to disk cache: " + loc.toAbsolutePath());
+      return null;
+    }
+    log.info("Enabling disk cache " + loc.toAbsolutePath());
+    return loc;
+  }
+
   @Override
   public void start() {
     if (executor != null) {
@@ -213,8 +220,7 @@
       TypeLiteral<K> keyType,
       long maxSize,
       Long expireAfterWrite) {
-    File db = new File(cacheDir, name).getAbsoluteFile();
-    String url = "jdbc:h2:" + db.toURI().toString();
+    String url = "jdbc:h2:" + cacheDir.resolve(name).toUri();
     return new SqlStore<>(url, keyType, maxSize,
         expireAfterWrite == null ? 0 : expireAfterWrite.longValue());
   }
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index 88e503e..484136a 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -51,6 +51,7 @@
     '//lib:guava',
     '//lib/jgit:jgit',
     '//lib/joda:joda-time',
+    '//lib/log:api',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java
index bed10d6..2335b8d1 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/FileUtil.java
@@ -21,6 +21,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 
 public class FileUtil {
@@ -42,6 +44,11 @@
     }
   }
 
+  public static void chmod(final int mode, final Path path) {
+    // TODO(dborowitz): Is there a portable way to do this with NIO?
+    chmod(mode, path.toFile());
+  }
+
   public static void chmod(final int mode, final File path) {
     path.setReadable(false, false /* all */);
     path.setWritable(false, false /* all */);
@@ -61,6 +68,33 @@
     }
   }
 
+  /**
+   * Get the last modified time of a path.
+   * <p>
+   * Equivalent to {@code File#lastModified()}, returning 0 on errors, including
+   * file not found. Callers that prefer exceptions can use {@link
+   * Files#getLastModifiedTime(Path, java.nio.file.LinkOption...)}.
+   *
+   * @param p path.
+   * @return last modified time, in milliseconds since epoch.
+   */
+  public static long lastModified(Path p) {
+    try {
+      return Files.getLastModifiedTime(p).toMillis();
+    } catch (IOException e) {
+      return 0;
+    }
+  }
+
+  public static Path mkdirsOrDie(Path p, String errMsg) {
+    try {
+      Files.createDirectories(p);
+      return p;
+    } catch (IOException e) {
+      throw new Die(errMsg + ": " + p, e);
+    }
+  }
+
   private FileUtil() {
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
index c45d9f9..9a30696 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/IoUtil.java
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.Sets;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -25,6 +24,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Set;
 
@@ -53,7 +53,7 @@
     }.start();
   }
 
-  public static void loadJARs(File... jars) {
+  public static void loadJARs(Iterable<Path> jars) {
     ClassLoader cl = IoUtil.class.getClassLoader();
     if (!(cl instanceof URLClassLoader)) {
       throw noAddURL("Not loaded by URLClassLoader", null);
@@ -71,9 +71,9 @@
     }
 
     Set<URL> have = Sets.newHashSet(Arrays.asList(urlClassLoader.getURLs()));
-    for (File path : jars) {
+    for (Path path : jars) {
       try {
-        URL url = path.toURI().toURL();
+        URL url = path.toUri().toURL();
         if (have.add(url)) {
           addURL.invoke(cl, url);
         }
@@ -86,6 +86,10 @@
     }
   }
 
+  public static void loadJARs(Path... jars) {
+    loadJARs(Arrays.asList(jars));
+  }
+
   private static UnsupportedOperationException noAddURL(String m, Throwable why) {
     String prefix = "Cannot extend classpath: ";
     return new UnsupportedOperationException(prefix + m, why);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
index ffdae9d..27dc639 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
@@ -14,18 +14,18 @@
 
 package com.google.gerrit.common;
 
-import java.io.File;
+import java.nio.file.Path;
 import java.util.Objects;
 
 public class PluginData {
   public final String name;
   public final String version;
-  public final File pluginFile;
+  public final Path pluginPath;
 
-  public PluginData(String name, String version, File pluginFile) {
+  public PluginData(String name, String version, Path pluginPath) {
     this.name = name;
     this.version = version;
-    this.pluginFile = pluginFile;
+    this.pluginPath = pluginPath;
   }
 
   @Override
@@ -33,13 +33,13 @@
     if (obj instanceof PluginData) {
       PluginData o = (PluginData) obj;
       return Objects.equals(name, o.name) && Objects.equals(version, o.version)
-          && Objects.equals(pluginFile, o.pluginFile);
+          && Objects.equals(pluginPath, o.pluginPath);
     }
     return super.equals(obj);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(name, version, pluginFile);
+    return Objects.hash(name, version, pluginPath);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
index a98e0a5..24451a4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/SiteLibraryLoaderUtil.java
@@ -14,41 +14,53 @@
 
 package com.google.gerrit.common;
 
-import java.io.File;
-import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.Comparator;
+import static com.google.gerrit.common.FileUtil.lastModified;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
 
 public final class SiteLibraryLoaderUtil {
+  private static final Logger log =
+      LoggerFactory.getLogger(SiteLibraryLoaderUtil.class);
 
-  public static void loadSiteLib(File libdir) {
-    File[] jars = listJars(libdir);
-    if (jars != null && 0 < jars.length) {
-      Arrays.sort(jars, new Comparator<File>() {
-        @Override
-        public int compare(File a, File b) {
-          // Sort by reverse last-modified time so newer JARs are first.
-          int cmp = Long.compare(b.lastModified(), a.lastModified());
-          if (cmp != 0) {
-            return cmp;
-          }
-          return a.getName().compareTo(b.getName());
-        }
-      });
-      IoUtil.loadJARs(jars);
+  public static void loadSiteLib(Path libdir) {
+    try {
+      IoUtil.loadJARs(listJars(libdir));
+    } catch (IOException e) {
+      log.error("Error scanning lib directory " + libdir, e);
     }
   }
 
-  public static File[] listJars(File libdir) {
-    File[] jars = libdir.listFiles(new FileFilter() {
+  public static List<Path> listJars(Path dir) throws IOException {
+    DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
       @Override
-      public boolean accept(File path) {
-        String name = path.getName();
-        return (name.endsWith(".jar") || name.endsWith(".zip"))
-            && path.isFile();
+      public boolean accept(Path entry) throws IOException {
+          String name = entry.getFileName().toString();
+          return (name.endsWith(".jar") || name.endsWith(".zip"))
+              && Files.isRegularFile(entry);
       }
-    });
-    return jars;
+    };
+    try (DirectoryStream<Path> jars = Files.newDirectoryStream(dir, filter)) {
+      return new Ordering<Path>() {
+        @Override
+        public int compare(Path a, Path b) {
+          // Sort by reverse last-modified time so newer JARs are first.
+          return ComparisonChain.start()
+              .compare(lastModified(b), lastModified(a))
+              .compare(a, b)
+              .result();
+        }
+      }.sortedCopy(jars);
+    }
   }
 
   private SiteLibraryLoaderUtil() {
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index a0d9455..d0204e4 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
index 75238a8..4893beff 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
@@ -18,24 +18,25 @@
 
 import com.google.inject.BindingAnnotation;
 
-import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
 
 /**
  * Local path where a plugin can store its own private data.
  * <p>
  * A plugin or extension may receive this string by Guice injection to discover
  * a directory where it can store configuration or other data that is private:
+ * <p>
+ * This binding is on both {@link java.io.File} and {@link java.nio.file.Path},
+ * pointing to the same location. The {@code File} version should be considered
+ * deprecated and may be removed in a future version.
  *
  * <pre>
  * {@literal @Inject}
- * MyType(@PluginData java.io.File myDir) {
- *   new FileInputStream(new File(myDir, &quot;my.config&quot;));
+ * MyType(@PluginData java.nio.file.Path myDir) {
+ *   this.in = Files.newInputStream(myDir.resolve(&quot;my.config&quot;));
  * }
  * </pre>
  */
-@Target({ElementType.PARAMETER, ElementType.FIELD})
 @Retention(RUNTIME)
 @BindingAnnotation
 public @interface PluginData {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index 71a93d3..32f8488 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -14,9 +14,12 @@
 
 package com.google.gerrit.extensions.api.accounts;
 
+import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
+import java.util.List;
+
 public interface Accounts {
   /**
    * Look up an account by ID.
@@ -42,6 +45,69 @@
   AccountApi self() throws RestApiException;
 
   /**
+   * Suggest users for a given query.
+   * <p>
+   * Example code:
+   * {@code suggestAccounts().withQuery("Reviewer").withLimit(5).get()}
+   *
+   * @return API for setting parameters and getting result.
+   */
+  SuggestAccountsRequest suggestAccounts() throws RestApiException;
+
+  /**
+   * Suggest users for a given query.
+   * <p>
+   * Shortcut API for {@code suggestAccounts().withQuery(String)}.
+   *
+   * @see #suggestAccounts()
+   */
+  SuggestAccountsRequest suggestAccounts(String query)
+    throws RestApiException;
+
+  /**
+   * API for setting parameters and getting result.
+   * Used for {@code suggestAccounts()}.
+   *
+   * @see #suggestAccounts()
+   */
+  public abstract class SuggestAccountsRequest {
+    private String query;
+    private int limit;
+
+    /**
+     * Executes query and returns a list of accounts.
+     */
+    public abstract List<AccountInfo> get() throws RestApiException;
+
+    /**
+     * Set query.
+     *
+     * @param query needs to be in human-readable form.
+     */
+    public SuggestAccountsRequest withQuery(String query) {
+      this.query = query;
+      return this;
+    }
+
+    /**
+     * Set limit for returned list of accounts.
+     * Optional; server-default is used when not provided.
+     */
+    public SuggestAccountsRequest withLimit(int limit) {
+      this.limit = limit;
+      return this;
+    }
+
+    public String getQuery() {
+      return query;
+    }
+
+    public int getLimit() {
+      return limit;
+    }
+  }
+
+  /**
    * A default implementation which allows source compatibility
    * when adding new methods to the interface.
    **/
@@ -55,5 +121,16 @@
     public AccountApi self() throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public SuggestAccountsRequest suggestAccounts() throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public SuggestAccountsRequest suggestAccounts(String query)
+      throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index 525684d..37b90c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -65,7 +65,6 @@
   private String message;
   private String branch;
   private String key;
-  private boolean canSubmit;
 
   Actions() {
     initWidget(uiBinder.createAndBindUi(this));
@@ -87,7 +86,12 @@
     changeInfo = info;
 
     initChangeActions(info, hasUser);
-    initRevisionActions(info, revInfo, hasUser);
+
+    NativeMap<ActionInfo> actionMap = revInfo.has_actions()
+        ? revInfo.actions()
+        : NativeMap.<ActionInfo> create();
+    actionMap.copyKeysIntoChildren("id");
+    reloadRevisionActions(actionMap);
   }
 
   private void initChangeActions(ChangeInfo info, boolean hasUser) {
@@ -107,30 +111,29 @@
     }
   }
 
-  private void initRevisionActions(ChangeInfo info, RevisionInfo revInfo,
-      boolean hasUser) {
-    NativeMap<ActionInfo> actions = revInfo.has_actions()
-        ? revInfo.actions()
-        : NativeMap.<ActionInfo> create();
-    actions.copyKeysIntoChildren("id");
+  void reloadRevisionActions(NativeMap<ActionInfo> actions) {
+    if (!Gerrit.isSignedIn()) {
+      return;
+    }
+    boolean canSubmit = actions.containsKey("submit");
+    if (canSubmit) {
+      ActionInfo action = actions.get("submit");
+      submit.setTitle(action.title());
+      submit.setEnabled(action.enabled());
+      submit.setHTML(new SafeHtmlBuilder()
+          .openDiv()
+          .append(action.label())
+          .closeDiv());
+      submit.setEnabled(action.enabled());
+    }
+    submit.setVisible(canSubmit);
 
-    canSubmit = false;
-    if (hasUser) {
-      canSubmit = actions.containsKey("submit");
-      if (canSubmit) {
-        ActionInfo action = actions.get("submit");
-        submit.setTitle(action.title());
-        submit.setEnabled(action.enabled());
-        submit.setHTML(new SafeHtmlBuilder()
-            .openDiv()
-            .append(action.label())
-            .closeDiv());
-      }
-      a2b(actions, "cherrypick", cherrypick);
-      a2b(actions, "rebase", rebase);
-      for (String id : filterNonCore(actions)) {
-        add(new ActionButton(info, revInfo, actions.get(id)));
-      }
+    a2b(actions, "cherrypick", cherrypick);
+    a2b(actions, "rebase", rebase);
+
+    RevisionInfo revInfo = changeInfo.revision(revision);
+    for (String id : filterNonCore(actions)) {
+      add(new ActionButton(changeInfo, revInfo, actions.get(id)));
     }
   }
 
@@ -146,10 +149,6 @@
     return ids;
   }
 
-  void setSubmitEnabled() {
-    submit.setVisible(canSubmit);
-  }
-
   @UiHandler("followUp")
   void onFollowUp(@SuppressWarnings("unused") ClickEvent e) {
     if (followUpAction == null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 8a27fd0..a6635f3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -134,8 +134,6 @@
   private CommentLinkProcessor commentLinkProcessor;
   private EditInfo edit;
 
-  private KeyCommandSet keysNavigation;
-  private KeyCommandSet keysAction;
   private List<HandlerRegistration> handlers = new ArrayList<>(4);
   private UpdateCheckTimer updateCheck;
   private Timestamp lastDisplayedUpdate;
@@ -248,7 +246,7 @@
   void loadChangeInfo(boolean fg, AsyncCallback<ChangeInfo> cb) {
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
-      ListChangesOption.CURRENT_ACTIONS,
+      ListChangesOption.CHANGE_ACTIONS,
       ListChangesOption.ALL_REVISIONS));
     if (!fg) {
       call.background();
@@ -256,6 +254,18 @@
     call.get(cb);
   }
 
+  void loadRevisionInfo() {
+    RestApi call = ChangeApi.actions(changeId.get(), revision);
+    call.background();
+    call.get(new GerritCallback<NativeMap<ActionInfo>>() {
+      @Override
+      public void onSuccess(NativeMap<ActionInfo> actionMap) {
+        actionMap.copyKeysIntoChildren("id");
+        renderRevisionInfo(changeInfo, actionMap);
+      }
+    });
+  }
+
   @Override
   protected void onUnload() {
     if (replyAction != null) {
@@ -281,69 +291,6 @@
     labels.init(style);
     reviewers.init(style, ccText);
     hashtags.init(style);
-
-    keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
-    keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
-      @Override
-      public void onKeyPress(final KeyPressEvent event) {
-        Gerrit.displayLastChangeList();
-      }
-    });
-    keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadChange()) {
-      @Override
-      public void onKeyPress(final KeyPressEvent event) {
-        Gerrit.display(PageLinks.toChange(changeId));
-      }
-    });
-    keysNavigation.add(new KeyCommand(0, 'n', Util.C.keyNextPatchSet()) {
-        @Override
-        public void onKeyPress(final KeyPressEvent event) {
-          gotoSibling(1);
-        }
-      }, new KeyCommand(0, 'p', Util.C.keyPreviousPatchSet()) {
-        @Override
-        public void onKeyPress(final KeyPressEvent event) {
-          gotoSibling(-1);
-        }
-      });
-
-    keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
-    keysAction.add(new KeyCommand(0, 'a', Util.C.keyPublishComments()) {
-      @Override
-      public void onKeyPress(KeyPressEvent event) {
-        if (Gerrit.isSignedIn()) {
-          onReply(null);
-        } else {
-          Gerrit.doSignIn(getToken());
-        }
-      }
-    });
-    keysAction.add(new KeyCommand(0, 'x', Util.C.keyExpandAllMessages()) {
-      @Override
-      public void onKeyPress(KeyPressEvent event) {
-        onExpandAll(null);
-      }
-    });
-    keysAction.add(new KeyCommand(0, 'z', Util.C.keyCollapseAllMessages()) {
-      @Override
-      public void onKeyPress(KeyPressEvent event) {
-        onCollapseAll(null);
-      }
-    });
-    if (Gerrit.isSignedIn()) {
-      keysAction.add(new KeyCommand(0, 's', Util.C.changeTableStar()) {
-        @Override
-        public void onKeyPress(KeyPressEvent event) {
-          star.setValue(!star.getValue(), true);
-        }
-      });
-      keysAction.add(new KeyCommand(0, 'c', Util.C.keyAddReviewers()) {
-        @Override
-        public void onKeyPress(KeyPressEvent event) {
-          reviewers.onOpenForm();
-        }
-      });
-    }
   }
 
   private void initReplyButton(ChangeInfo info, String revision) {
@@ -403,7 +350,8 @@
     }
   }
 
-  private void initRevisionsAction(ChangeInfo info, String revision) {
+  private void initRevisionsAction(ChangeInfo info, String revision,
+      NativeMap<ActionInfo> actions) {
     int currentPatchSet;
     if (info.current_revision() != null
         && info.revisions().containsKey(info.current_revision())) {
@@ -431,11 +379,6 @@
 
     RevisionInfo revInfo = info.revision(revision);
     if (revInfo.draft()) {
-      NativeMap<ActionInfo> actions = revInfo.has_actions()
-          ? revInfo.actions()
-          : NativeMap.<ActionInfo> create();
-      actions.copyKeysIntoChildren("id");
-
       if (actions.containsKey("publish")) {
         publish.setVisible(true);
         publish.setTitle(actions.get("publish").title());
@@ -566,7 +509,88 @@
   @Override
   public void registerKeys() {
     super.registerKeys();
+
+    KeyCommandSet keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
+    keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
+      @Override
+      public void onKeyPress(final KeyPressEvent event) {
+        Gerrit.displayLastChangeList();
+      }
+    });
+    keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadChange()) {
+      @Override
+      public void onKeyPress(final KeyPressEvent event) {
+        Gerrit.display(PageLinks.toChange(changeId));
+      }
+    });
+    keysNavigation.add(new KeyCommand(0, 'n', Util.C.keyNextPatchSet()) {
+        @Override
+        public void onKeyPress(final KeyPressEvent event) {
+          gotoSibling(1);
+        }
+      }, new KeyCommand(0, 'p', Util.C.keyPreviousPatchSet()) {
+        @Override
+        public void onKeyPress(final KeyPressEvent event) {
+          gotoSibling(-1);
+        }
+      });
     handlers.add(GlobalKey.add(this, keysNavigation));
+
+    KeyCommandSet keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
+    keysAction.add(new KeyCommand(0, 'a', Util.C.keyPublishComments()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        if (Gerrit.isSignedIn()) {
+          onReply(null);
+        } else {
+          Gerrit.doSignIn(getToken());
+        }
+      }
+    });
+    keysAction.add(new KeyCommand(0, 'x', Util.C.keyExpandAllMessages()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        onExpandAll(null);
+      }
+    });
+    keysAction.add(new KeyCommand(0, 'z', Util.C.keyCollapseAllMessages()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        onCollapseAll(null);
+      }
+    });
+    keysAction.add(new KeyCommand(0, 's', Util.C.changeTableStar()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        if (Gerrit.isSignedIn()) {
+          star.setValue(!star.getValue(), true);
+        } else {
+          Gerrit.doSignIn(getToken());
+        }
+      }
+    });
+    keysAction.add(new KeyCommand(0, 'c', Util.C.keyAddReviewers()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        if (Gerrit.isSignedIn()) {
+          reviewers.onOpenForm();
+        } else {
+          Gerrit.doSignIn(getToken());
+        }
+      }
+    });
+    keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        if (Gerrit.isSignedIn()) {
+          if (topic.canEdit()) {
+            topic.onEdit();
+          }
+        } else {
+          Gerrit.doSignIn(getToken());
+        }
+      }
+    });
     handlers.add(GlobalKey.add(this, keysAction));
     files.registerKeys();
   }
@@ -806,6 +830,7 @@
           commentLinkProcessor = result.getCommentLinkProcessor();
           setTheme(result.getTheme());
           renderChangeInfo(info);
+          loadRevisionInfo();
         }
       }));
   }
@@ -933,7 +958,6 @@
 
   private void loadSubmitType(final Change.Status status, final boolean canSubmit) {
     if (canSubmit) {
-      actions.setSubmitEnabled();
       if (status == Change.Status.NEW) {
         statusText.setInnerText(Util.C.readyToSubmit());
       }
@@ -1043,19 +1067,7 @@
   private void renderChangeInfo(ChangeInfo info) {
     changeInfo = info;
     lastDisplayedUpdate = info.updated();
-    RevisionInfo revisionInfo = info.revision(revision);
-    boolean current = info.status().isOpen()
-        && revision.equals(info.current_revision())
-        && !revisionInfo.is_edit();
 
-    if (revisionInfo.is_edit()) {
-      statusText.setInnerText(Util.C.changeEdit());
-    } else if (!current && info.status() == Change.Status.NEW) {
-      statusText.setInnerText(Util.C.notCurrent());
-      labels.setVisible(false);
-    } else {
-      statusText.setInnerText(Util.toLongString(info.status()));
-    }
     labels.set(info);
 
     renderOwner(info);
@@ -1064,7 +1076,6 @@
     initReplyButton(info, revision);
     initIncludedInAction(info);
     initChangeAction(info);
-    initRevisionsAction(info, revision);
     initDownloadAction(info, revision);
     initProjectLinks(info);
     initBranchLink(info);
@@ -1084,17 +1095,44 @@
       setVisible(hashtagTableRow, false);
     }
 
+    StringBuilder sb = new StringBuilder();
+    sb.append(Util.M.changeScreenTitleId(info.id_abbreviated()));
+    if (info.subject() != null) {
+      sb.append(": ");
+      sb.append(info.subject());
+    }
+    setWindowTitle(sb.toString());
+
+    // Properly render revision actions initially while waiting for
+    // the callback to populate them correctly.
+    NativeMap<ActionInfo> emptyMap = NativeMap.<ActionInfo> create();
+    initRevisionsAction(info, revision, emptyMap);
+    quickApprove.setVisible(false);
+    setVisible(strategy, false);
+    actions.reloadRevisionActions(emptyMap);
+  }
+
+  private void renderRevisionInfo(ChangeInfo info,
+      NativeMap<ActionInfo> actionMap) {
+    RevisionInfo revisionInfo = info.revision(revision);
+    boolean current = info.status().isOpen()
+        && revision.equals(info.current_revision())
+        && !revisionInfo.is_edit();
+
+    if (revisionInfo.is_edit()) {
+      statusText.setInnerText(Util.C.changeEdit());
+    } else if (!current && info.status() == Change.Status.NEW) {
+      statusText.setInnerText(Util.C.notCurrent());
+      labels.setVisible(false);
+    } else {
+      statusText.setInnerText(Util.toLongString(info.status()));
+    }
+
+    initRevisionsAction(info, revision, actionMap);
+
     if (Gerrit.isSignedIn()) {
       replyAction = new ReplyAction(info, revision,
           style, commentLinkProcessor, reply, quickApprove);
-      if (topic.canEdit()) {
-        keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
-          @Override
-          public void onKeyPress(KeyPressEvent event) {
-            topic.onEdit();
-          }
-        });
-      }
     }
     history.set(commentLinkProcessor, replyAction, changeId, info);
 
@@ -1105,14 +1143,7 @@
       quickApprove.setVisible(false);
       setVisible(strategy, false);
     }
-
-    StringBuilder sb = new StringBuilder();
-    sb.append(Util.M.changeScreenTitleId(info.id_abbreviated()));
-    if (info.subject() != null) {
-      sb.append(": ");
-      sb.append(info.subject());
-    }
-    setWindowTitle(sb.toString());
+    actions.reloadRevisionActions(actionMap);
   }
 
   private void renderOwner(ChangeInfo info) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 98595e7..1135491 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -95,6 +95,13 @@
     return call(id, "detail");
   }
 
+  public static RestApi actions(int id, String revision) {
+    if (revision == null || revision.equals("")) {
+      revision = "current";
+    }
+    return call(id, revision, "actions");
+  }
+
   public static void edit(int id, AsyncCallback<EditInfo> cb) {
     edit(id).get(cb);
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
index 9d47977..c19c817 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.httpd;
 
+import static java.nio.file.Files.isExecutable;
+import static java.nio.file.Files.isRegularFile;
+
 import com.google.gerrit.common.data.GitWebType;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -23,16 +26,17 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 public class GitWebConfig {
   private static final Logger log = LoggerFactory.getLogger(GitWebConfig.class);
 
   private final String url;
-  private final File gitweb_cgi;
-  private final File gitweb_css;
-  private final File gitweb_js;
-  private final File git_logo_png;
+  private final Path gitweb_cgi;
+  private final Path gitweb_css;
+  private final Path gitweb_js;
+  private final Path git_logo_png;
   private GitWebType type;
 
   @Inject
@@ -117,20 +121,20 @@
       return;
     }
 
-    final File pkgCgi = new File("/usr/lib/cgi-bin/gitweb.cgi");
+    final Path pkgCgi = Paths.get("/usr/lib/cgi-bin/gitweb.cgi");
     String[] resourcePaths = {"/usr/share/gitweb/static", "/usr/share/gitweb",
         "/var/www/static", "/var/www"};
-    File cgi;
+    Path cgi;
 
     if (cfgCgi != null) {
       // Use the CGI script configured by the administrator, failing if it
       // cannot be used as specified.
       //
-      cgi = sitePaths.resolve(cfgCgi);
-      if (!cgi.isFile()) {
+      cgi = sitePaths.resolve(cfgCgi).toPath();
+      if (!isRegularFile(cgi)) {
         throw new IllegalStateException("Cannot find gitweb.cgi: " + cgi);
       }
-      if (!cgi.canExecute()) {
+      if (!isExecutable(cgi)) {
         throw new IllegalStateException("Cannot execute gitweb.cgi: " + cgi);
       }
 
@@ -138,11 +142,11 @@
         // Assume the administrator pointed us to the distribution,
         // which also has the corresponding CSS and logo file.
         //
-        String absPath = cgi.getParentFile().getAbsolutePath();
+        String absPath = cgi.getParent().toAbsolutePath().toString();
         resourcePaths = new String[] {absPath + "/static", absPath};
       }
 
-    } else if (pkgCgi.isFile() && pkgCgi.canExecute()) {
+    } else if (isRegularFile(pkgCgi) && isExecutable(pkgCgi)) {
       // Use the OS packaged CGI.
       //
       log.debug("Assuming gitweb at " + pkgCgi);
@@ -154,13 +158,13 @@
       resourcePaths = new String[] {};
     }
 
-    File css = null, js = null, logo = null;
+    Path css = null, js = null, logo = null;
     for (String path : resourcePaths) {
-      File dir = new File(path);
-      css = new File(dir, "gitweb.css");
-      js = new File(dir, "gitweb.js");
-      logo = new File(dir, "git-logo.png");
-      if (css.isFile() && logo.isFile()) {
+      Path dir = Paths.get(path);
+      css = dir.resolve("gitweb.css");
+      js = dir.resolve("gitweb.js");
+      logo = dir.resolve("git-logo.png");
+      if (isRegularFile(css) && isRegularFile(logo)) {
         break;
       }
     }
@@ -191,22 +195,22 @@
   }
 
   /** @return local path to the CGI executable; null if we shouldn't execute. */
-  public File getGitwebCGI() {
+  public Path getGitwebCGI() {
     return gitweb_cgi;
   }
 
   /** @return local path of the {@code gitweb.css} matching the CGI. */
-  public File getGitwebCSS() {
+  public Path getGitwebCSS() {
     return gitweb_css;
   }
 
   /** @return local path of the {@code gitweb.js} for the CGI. */
-  public File getGitwebJS() {
+  public Path getGitwebJS() {
     return gitweb_js;
   }
 
   /** @return local path of the {@code git-logo.png} for the CGI. */
-  public File getGitLogoPNG() {
+  public Path getGitLogoPNG() {
     return git_logo_png;
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
index 1a2d3f6..1eb88b1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.common.io.ByteStreams;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -21,14 +23,14 @@
 import org.xml.sax.SAXException;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
 import java.util.zip.GZIPOutputStream;
 
 import javax.xml.parsers.DocumentBuilder;
@@ -36,7 +38,6 @@
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
@@ -49,21 +50,21 @@
 /** Utility functions to deal with HTML using W3C DOM operations. */
 public class HtmlDomUtil {
   /** Standard character encoding we prefer (UTF-8). */
-  public static final String ENC = "UTF-8";
+  public static final Charset ENC = StandardCharsets.UTF_8;
 
   /** DOCTYPE for a standards mode HTML document. */
   public static final String HTML_STRICT =
       "-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd";
 
   /** Convert a document to a UTF-8 byte sequence. */
-  public static byte[] toUTF8(final Document hostDoc) throws IOException {
+  public static byte[] toUTF8(Document hostDoc) throws IOException {
     return toString(hostDoc).getBytes(ENC);
   }
 
   /** Compress the document. */
-  public static byte[] compress(final byte[] raw) throws IOException {
-    final ByteArrayOutputStream out = new ByteArrayOutputStream();
-    final GZIPOutputStream gz = new GZIPOutputStream(out);
+  public static byte[] compress(byte[] raw) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    GZIPOutputStream gz = new GZIPOutputStream(out);
     gz.write(raw);
     gz.finish();
     gz.flush();
@@ -71,43 +72,39 @@
   }
 
   /** Convert a document to a String, assuming later encoding to UTF-8. */
-  public static String toString(final Document hostDoc) throws IOException {
+  public static String toString(Document hostDoc) throws IOException {
     try {
-      final StringWriter out = new StringWriter();
-      final DOMSource domSource = new DOMSource(hostDoc);
-      final StreamResult streamResult = new StreamResult(out);
-      final TransformerFactory tf = TransformerFactory.newInstance();
-      final Transformer serializer = tf.newTransformer();
-      serializer.setOutputProperty(OutputKeys.ENCODING, ENC);
+      StringWriter out = new StringWriter();
+      DOMSource domSource = new DOMSource(hostDoc);
+      StreamResult streamResult = new StreamResult(out);
+      TransformerFactory tf = TransformerFactory.newInstance();
+      Transformer serializer = tf.newTransformer();
+      serializer.setOutputProperty(OutputKeys.ENCODING, ENC.name());
       serializer.setOutputProperty(OutputKeys.METHOD, "html");
       serializer.setOutputProperty(OutputKeys.INDENT, "no");
       serializer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
           HtmlDomUtil.HTML_STRICT);
       serializer.transform(domSource, streamResult);
       return out.toString();
-    } catch (TransformerConfigurationException e) {
-      final IOException r = new IOException("Error transforming page");
-      r.initCause(e);
-      throw r;
     } catch (TransformerException e) {
-      final IOException r = new IOException("Error transforming page");
+      IOException r = new IOException("Error transforming page");
       r.initCause(e);
       throw r;
     }
   }
 
   /** Find an element by its "id" attribute; null if no element is found. */
-  public static Element find(final Node parent, final String name) {
-    final NodeList list = parent.getChildNodes();
+  public static Element find(Node parent, String name) {
+    NodeList list = parent.getChildNodes();
     for (int i = 0; i < list.getLength(); i++) {
-      final Node n = list.item(i);
+      Node n = list.item(i);
       if (n instanceof Element) {
-        final Element e = (Element) n;
+        Element e = (Element) n;
         if (name.equals(e.getAttribute("id"))) {
           return e;
         }
       }
-      final Element r = find(n, name);
+      Element r = find(n, name);
       if (r != null) {
         return r;
       }
@@ -116,9 +113,8 @@
   }
 
   /** Append an HTML &lt;input type="hidden"&gt; to the form. */
-  public static void addHidden(final Element form, final String name,
-      final String value) {
-    final Element in = form.getOwnerDocument().createElement("input");
+  public static void addHidden(Element form, String name, String value) {
+    Element in = form.getOwnerDocument().createElement("input");
     in.setAttribute("type", "hidden");
     in.setAttribute("name", name);
     in.setAttribute("value", value);
@@ -135,51 +131,38 @@
   }
 
   /** Clone a document so it can be safely modified on a per-request basis. */
-  public static Document clone(final Document doc) throws IOException {
-    final Document d;
+  public static Document clone(Document doc) throws IOException {
+    Document d;
     try {
       d = newBuilder().newDocument();
     } catch (ParserConfigurationException e) {
       throw new IOException("Cannot clone document");
     }
-    final Node n = d.importNode(doc.getDocumentElement(), true);
+    Node n = d.importNode(doc.getDocumentElement(), true);
     d.appendChild(n);
     return d;
   }
 
   /** Parse an XHTML file from our CLASSPATH and return the instance. */
-  public static Document parseFile(final Class<?> context, final String name)
+  public static Document parseFile(Class<?> context, String name)
       throws IOException {
-    final InputStream in;
-
-    in = context.getResourceAsStream(name);
-    if (in == null) {
-      return null;
-    }
-    try {
-      try {
-        try {
-          final Document doc = newBuilder().parse(in);
-          compact(doc);
-          return doc;
-        } catch (SAXException e) {
-          throw new IOException("Error reading " + name, e);
-        } catch (ParserConfigurationException e) {
-          throw new IOException("Error reading " + name, e);
-        }
-      } finally {
-        in.close();
+    try (InputStream in = context.getResourceAsStream(name)) {
+      if (in == null) {
+        return null;
       }
-    } catch (IOException e) {
+      Document doc = newBuilder().parse(in);
+      compact(doc);
+      return doc;
+    } catch (SAXException | ParserConfigurationException | IOException e) {
       throw new IOException("Error reading " + name, e);
     }
   }
 
-  private static void compact(final Document doc) {
+  private static void compact(Document doc) {
     try {
-      final String expr = "//text()[normalize-space(.) = '']";
-      final XPathFactory xp = XPathFactory.newInstance();
-      final XPathExpression e = xp.newXPath().compile(expr);
+      String expr = "//text()[normalize-space(.) = '']";
+      XPathFactory xp = XPathFactory.newInstance();
+      XPathExpression e = xp.newXPath().compile(expr);
       NodeList empty = (NodeList) e.evaluate(doc, XPathConstants.NODESET);
       for (int i = 0; i < empty.getLength(); i++) {
         Node node = empty.item(i);
@@ -191,78 +174,50 @@
   }
 
   /** Read a Read a UTF-8 text file from our CLASSPATH and return it. */
-  public static String readFile(final Class<?> context, final String name)
+  public static String readFile(Class<?> context, String name)
       throws IOException {
-    final InputStream in = context.getResourceAsStream(name);
-    if (in == null) {
-      return null;
-    }
-    try {
-      return asString(in);
+    try (InputStream in = context.getResourceAsStream(name)) {
+      if (in == null) {
+        return null;
+      }
+      return new String(ByteStreams.toByteArray(in), ENC);
     } catch (IOException e) {
       throw new IOException("Error reading " + name, e);
     }
   }
 
   /** Parse an XHTML file from the local drive and return the instance. */
-  public static Document parseFile(final File path) throws IOException {
-    try {
-      final InputStream in = new FileInputStream(path);
-      try {
-        try {
-          final Document doc = newBuilder().parse(in);
-          compact(doc);
-          return doc;
-        } catch (SAXException e) {
-          throw new IOException("Error reading " + path, e);
-        } catch (ParserConfigurationException e) {
-          throw new IOException("Error reading " + path, e);
-        }
-      } finally {
-        in.close();
-      }
-    } catch (FileNotFoundException e) {
+  public static Document parseFile(Path path) throws IOException {
+    try (InputStream in = Files.newInputStream(path)) {
+      Document doc = newBuilder().parse(in);
+      compact(doc);
+      return doc;
+    } catch (NoSuchFileException e) {
       return null;
-    } catch (IOException e) {
+    } catch (SAXException | ParserConfigurationException | IOException e) {
       throw new IOException("Error reading " + path, e);
     }
   }
 
   /** Read a UTF-8 text file from the local drive. */
-  public static String readFile(final File parentDir, final String name)
+  public static String readFile(Path parentDir, String name)
       throws IOException {
     if (parentDir == null) {
       return null;
     }
-    final File path = new File(parentDir, name);
-    try {
-      return asString(new FileInputStream(path));
-    } catch (FileNotFoundException e) {
+    Path path = parentDir.resolve(name);
+    try (InputStream in = Files.newInputStream(path)) {
+      return new String(ByteStreams.toByteArray(in), ENC);
+    } catch (NoSuchFileException e) {
       return null;
     } catch (IOException e) {
       throw new IOException("Error reading " + path, e);
     }
   }
 
-  private static String asString(final InputStream in)
-      throws UnsupportedEncodingException, IOException {
-    try {
-      final StringBuilder w = new StringBuilder();
-      final InputStreamReader r = new InputStreamReader(in, ENC);
-      final char[] buf = new char[512];
-      int n;
-      while ((n = r.read(buf)) > 0) {
-        w.append(buf, 0, n);
-      }
-      return w.toString();
-    } finally {
-      in.close();
-    }
-  }
-
   private static DocumentBuilder newBuilder()
       throws ParserConfigurationException {
-    final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     factory.setValidating(false);
     factory.setExpandEntityReferences(false);
     factory.setIgnoringComments(true);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index b8a8092..ed84caf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -104,7 +104,7 @@
         throw new ServletException(e);
       }
       rsp.setContentType("text/html");
-      rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+      rsp.setCharacterEncoding(HtmlDomUtil.ENC.name());
       rsp.setContentLength(raw.length);
       final OutputStream out = rsp.getOutputStream();
       try {
@@ -138,7 +138,7 @@
 
     } else {
       rsp.setContentType("text/html");
-      rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+      rsp.setCharacterEncoding(HtmlDomUtil.ENC.name());
       final Writer out = rsp.getWriter();
       out.write("<html>");
       out.write("<body>");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index 19c8342..b5400b2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -110,7 +110,7 @@
 
       CacheHeaders.setNotCacheable(rsp);
       rsp.setContentType("text/html");
-      rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+      rsp.setCharacterEncoding(HtmlDomUtil.ENC.name());
       rsp.setContentLength(tosend.length);
       final OutputStream out = rsp.getOutputStream();
       try {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
index 41aa552..3396f2b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
@@ -14,16 +14,19 @@
 
 package com.google.gerrit.httpd.gitweb;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.httpd.GitWebConfig;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
-import org.eclipse.jgit.util.IO;
-
-import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletOutputStream;
@@ -38,16 +41,16 @@
   private final byte[] raw;
 
   @Inject
-  GitLogoServlet(final GitWebConfig gitWebConfig) throws IOException {
+  GitLogoServlet(GitWebConfig gitWebConfig) throws IOException {
     byte[] png;
-    final File src = gitWebConfig.getGitLogoPNG();
+    Path src = gitWebConfig.getGitLogoPNG();
     if (src != null) {
-      try {
-        png = IO.readFully(src);
-      } catch (FileNotFoundException e) {
+      try (InputStream in = Files.newInputStream(src)) {
+        png = ByteStreams.toByteArray(in);
+      } catch (NoSuchFileException e) {
         png = null;
       }
-      modified = src.lastModified();
+      modified = lastModified(src);
     } else {
       modified = -1;
       png = null;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
index 4a39b97..5625334 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.gitweb;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
 import com.google.gerrit.httpd.GitWebConfig;
 import com.google.gerrit.httpd.HtmlDomUtil;
 import com.google.gerrit.server.config.SitePaths;
@@ -22,8 +24,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletOutputStream;
@@ -55,14 +57,14 @@
   private final byte[] raw_css;
   private final byte[] gz_css;
 
-  GitWebCssServlet(final File src)
+  GitWebCssServlet(final Path src)
       throws IOException {
     if (src != null) {
-      final File dir = src.getParentFile();
-      final String name = src.getName();
+      final Path dir = src.getParent();
+      final String name = src.getFileName().toString();
       final String raw = HtmlDomUtil.readFile(dir, name);
       if (raw != null) {
-        modified = src.lastModified();
+        modified = lastModified(src);
         raw_css = raw.getBytes(ENC);
         gz_css = HtmlDomUtil.compress(raw_css);
       } else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebJavaScriptServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebJavaScriptServlet.java
index d71732a..6926afd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebJavaScriptServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebJavaScriptServlet.java
@@ -14,16 +14,19 @@
 
 package com.google.gerrit.httpd.gitweb;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.httpd.GitWebConfig;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
-import org.eclipse.jgit.util.IO;
-
-import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletOutputStream;
@@ -40,14 +43,14 @@
   @Inject
   GitWebJavaScriptServlet(final GitWebConfig gitWebConfig) throws IOException {
     byte[] png;
-    final File src = gitWebConfig.getGitwebJS();
+    Path src = gitWebConfig.getGitwebJS();
     if (src != null) {
-      try {
-        png = IO.readFully(src);
-      } catch (FileNotFoundException e) {
+      try (InputStream in = Files.newInputStream(src)) {
+        png = ByteStreams.toByteArray(in);
+      } catch (NoSuchFileException e) {
         png = null;
       }
-      modified = src.lastModified();
+      modified = lastModified(src);
     } else {
       modified = -1;
       png = null;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 9a7cab0..de619c3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -29,6 +29,8 @@
 
 package com.google.gerrit.httpd.gitweb;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.httpd.GitWebConfig;
@@ -54,7 +56,6 @@
 import java.io.BufferedReader;
 import java.io.EOFException;
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -62,6 +63,8 @@
 import java.io.PrintWriter;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -81,7 +84,7 @@
 
   private final Set<String> deniedActions;
   private final int bufferSize = 8192;
-  private final File gitwebCgi;
+  private final Path gitwebCgi;
   private final URI gitwebUrl;
   private final LocalDiskRepositoryManager repoManager;
   private final ProjectControl.Factory projectControl;
@@ -143,26 +146,28 @@
 
   private void makeSiteConfig(final SitePaths site,
       final GerritConfig gerritConfig) throws IOException {
-    if (!site.tmp_dir.exists()) {
-      site.tmp_dir.mkdirs();
+    if (!Files.exists(site.tmp_dir)) {
+      Files.createDirectories(site.tmp_dir);
     }
-    File myconf = File.createTempFile("gitweb_config", ".perl", site.tmp_dir);
+    Path myconf = Files.createTempFile(site.tmp_dir, "gitweb_config", ".perl");
 
     // To make our configuration file only readable or writable by us;
     // this reduces the chances of someone tampering with the file.
     //
-    myconf.setWritable(false, false /* all */);
-    myconf.setReadable(false, false /* all */);
-    myconf.setExecutable(false, false /* all */);
+    // TODO(dborowitz): Is there a portable way to do this with NIO?
+    File myconfFile = myconf.toFile();
+    myconfFile.setWritable(false, false /* all */);
+    myconfFile.setReadable(false, false /* all */);
+    myconfFile.setExecutable(false, false /* all */);
 
-    myconf.setWritable(true, true /* owner only */);
-    myconf.setReadable(true, true /* owner only */);
+    myconfFile.setWritable(true, true /* owner only */);
+    myconfFile.setReadable(true, true /* owner only */);
 
     _env.set("GIT_DIR", ".");
-    _env.set("GITWEB_CONFIG", myconf.getAbsolutePath());
+    _env.set("GITWEB_CONFIG", myconf.toAbsolutePath().toString());
 
-    final PrintWriter p = new PrintWriter(new FileWriter(myconf));
-    try {
+    try (PrintWriter p =
+        new PrintWriter(Files.newBufferedWriter(myconf, UTF_8))) {
       p.print("# Autogenerated by Gerrit Code Review \n");
       p.print("# DO NOT EDIT\n");
       p.print("\n");
@@ -170,12 +175,12 @@
       // We are mounted at the same level in the context as the main
       // UI, so we can include the same header and footer scheme.
       //
-      final File hdr = site.site_header;
-      if (hdr.isFile()) {
+      Path hdr = site.site_header;
+      if (Files.isRegularFile(hdr)) {
         p.print("$site_header = " + quoteForPerl(hdr) + ";\n");
       }
-      final File ftr = site.site_footer;
-      if (ftr.isFile()) {
+      Path ftr = site.site_footer;
+      if (Files.isRegularFile(ftr)) {
         p.print("$site_footer = " + quoteForPerl(ftr) + ";\n");
       }
 
@@ -188,8 +193,8 @@
       p.print("$logo = 'gitweb-logo.png';\n");
       p.print("$javascript = 'gitweb.js';\n");
       p.print("@stylesheets = ('gitweb-default.css');\n");
-      final File css = site.site_css;
-      if (css.isFile()) {
+      Path css = site.site_css;
+      if (Files.isRegularFile(css)) {
         p.print("push @stylesheets, 'gitweb-site.css';\n");
       }
 
@@ -290,15 +295,15 @@
       // If the administrator has created a site-specific gitweb_config,
       // load that before we perform any final overrides.
       //
-      final File sitecfg = site.site_gitweb;
-      if (sitecfg.isFile()) {
+      Path sitecfg = site.site_gitweb;
+      if (Files.isRegularFile(sitecfg)) {
         p.print("$GITWEB_CONFIG = " + quoteForPerl(sitecfg) + ";\n");
         p.print("if (-e $GITWEB_CONFIG) {\n");
         p.print("  do " + quoteForPerl(sitecfg) + ";\n");
         p.print("}\n");
       }
 
-      final File root = repoManager.getBasePath();
+      Path root = repoManager.getBasePath().toPath();
       p.print("$projectroot = " + quoteForPerl(root) + ";\n");
 
       // Permit exporting only the project we were started for.
@@ -322,18 +327,16 @@
       //
       p.print("$feature{'forks'}{'override'} = 0;\n");
       p.print("$feature{'forks'}{'default'} = [0];\n");
-    } finally {
-      p.close();
     }
 
-    myconf.setReadOnly();
+    myconfFile.setReadOnly();
   }
 
-  private String quoteForPerl(File value) {
-    return quoteForPerl(value.getAbsolutePath());
+  private static String quoteForPerl(Path value) {
+    return quoteForPerl(value.toAbsolutePath().toString());
   }
 
-  private String quoteForPerl(String value) {
+  private static String quoteForPerl(String value) {
     if (value == null || value.isEmpty()) {
       return "''";
     }
@@ -442,9 +445,10 @@
   private void exec(final HttpServletRequest req,
       final HttpServletResponse rsp, final ProjectControl project) throws IOException {
     final Process proc =
-        Runtime.getRuntime().exec(new String[] {gitwebCgi.getAbsolutePath()},
+        Runtime.getRuntime().exec(
+            new String[] {gitwebCgi.toAbsolutePath().toString()},
             makeEnv(req, project),
-            gitwebCgi.getAbsoluteFile().getParentFile());
+            gitwebCgi.toAbsolutePath().getParent().toFile());
 
     copyStderrToLog(proc.getErrorStream());
     if (0 < req.getContentLength()) {
@@ -522,7 +526,7 @@
     //
     env.set("REQUEST_METHOD", req.getMethod());
     env.set("SCRIPT_NAME", req.getContextPath() + req.getServletPath());
-    env.set("SCRIPT_FILENAME", gitwebCgi.getAbsolutePath());
+    env.set("SCRIPT_FILENAME", gitwebCgi.toAbsolutePath().toString());
     env.set("SERVER_NAME", req.getServerName());
     env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
     env.set("SERVER_PROTOCOL", req.getProtocol());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 31ff107..fd26837 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.httpd.plugins;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
 import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CHARACTER_ENCODING;
 import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CONTENT_TYPE;
 
@@ -24,6 +25,7 @@
 import com.google.common.cache.Cache;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
 import com.google.common.net.HttpHeaders;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.httpd.resources.Resource;
@@ -55,14 +57,14 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
@@ -303,7 +305,7 @@
       }
       if (!entry.isPresent() && file.endsWith("/index.html")) {
         String pfx = file.substring(0, file.length() - "index.html".length());
-        long pluginLastModified = holder.plugin.getSrcFile().lastModified();
+        long pluginLastModified = lastModified(holder.plugin.getSrcFile());
         if (hasUpToDateCachedResource(rsc, pluginLastModified)) {
           rsc.send(req, res);
         } else {
@@ -608,12 +610,12 @@
 
   private void sendJsPlugin(Plugin plugin, PluginResourceKey key,
       HttpServletRequest req, HttpServletResponse res) throws IOException {
-    File pluginFile = plugin.getSrcFile();
+    Path path = plugin.getSrcFile();
     if (req.getRequestURI().endsWith(getJsPluginPath(plugin))
-        && pluginFile.exists()) {
-      res.setHeader("Content-Length", Long.toString(pluginFile.length()));
+        && Files.exists(path)) {
+      res.setHeader("Content-Length", Long.toString(Files.size(path)));
       res.setContentType("application/javascript");
-      writeToResponse(res, new FileInputStream(pluginFile));
+      writeToResponse(res, Files.newInputStream(path));
     } else {
       resourceCache.put(key, Resource.NOT_FOUND);
       Resource.NOT_FOUND.send(req, res);
@@ -621,25 +623,15 @@
   }
 
   private static String getJsPluginPath(Plugin plugin) {
-    return String.format("/plugins/%s/static/%s", plugin.getName(), plugin.getSrcFile()
-        .getName());
+    return String.format("/plugins/%s/static/%s", plugin.getName(),
+        plugin.getSrcFile().getFileName());
   }
 
-  private void writeToResponse(HttpServletResponse res, InputStream in)
+  private void writeToResponse(HttpServletResponse res, InputStream inputStream)
       throws IOException {
-    try {
-      OutputStream out = res.getOutputStream();
-      try {
-        byte[] tmp = new byte[1024];
-        int n;
-        while ((n = in.read(tmp)) > 0) {
-          out.write(tmp, 0, n);
-        }
-      } finally {
-        out.close();
-      }
-    } finally {
-      in.close();
+    try (OutputStream out = res.getOutputStream();
+        InputStream in = inputStream) {
+      ByteStreams.copy(in, out);
     }
   }
 
@@ -671,9 +663,9 @@
     }
 
     private static String getPrefix(Plugin plugin, String attr, String def) {
-      File srcFile = plugin.getSrcFile();
+      Path path = plugin.getSrcFile();
       PluginContentScanner scanner = plugin.getContentScanner();
-      if (srcFile == null || scanner == PluginContentScanner.EMPTY) {
+      if (path == null || scanner == PluginContentScanner.EMPTY) {
         return def;
       }
       try {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 90c5ff4..1bc1125 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.raw;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.hash.Hasher;
@@ -47,12 +49,12 @@
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StringWriter;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -160,16 +162,13 @@
 
   private Page get() {
     Page p = page;
-    if (refreshHeaderFooter && p.isStale()) {
-      final Page newPage;
-      try {
-        newPage = new Page();
-      } catch (IOException e) {
-        log.error("Cannot refresh site header/footer", e);
-        return p;
+    try {
+      if (refreshHeaderFooter && p.isStale()) {
+        p = new Page();
+        page = p;
       }
-      p = newPage;
-      page = p;
+    } catch (IOException e) {
+      log.error("Cannot refresh site header/footer", e);
     }
     return p;
   }
@@ -216,7 +215,7 @@
 
     CacheHeaders.setNotCacheable(rsp);
     rsp.setContentType("text/html");
-    rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+    rsp.setCharacterEncoding(HtmlDomUtil.ENC.name());
     rsp.setContentLength(tosend.length);
     final OutputStream out = rsp.getOutputStream();
     try {
@@ -288,16 +287,16 @@
   }
 
   private static class FileInfo {
-    private final File path;
+    private final Path path;
     private final long time;
 
-    FileInfo(final File p) {
+    FileInfo(Path p) {
       path = p;
-      time = path.lastModified();
+      time = lastModified(path);
     }
 
     boolean isStale() {
-      return time != path.lastModified();
+      return time != lastModified(path);
     }
   }
 
@@ -364,8 +363,8 @@
       }
     }
 
-    private FileInfo injectCssFile(final Document hostDoc, final String id,
-        final File src) throws IOException {
+    private FileInfo injectCssFile(Document hostDoc, String id, Path src)
+        throws IOException {
       final FileInfo info = new FileInfo(src);
       final Element banner = HtmlDomUtil.find(hostDoc, id);
       if (banner == null) {
@@ -376,7 +375,8 @@
         banner.removeChild(banner.getFirstChild());
       }
 
-      String css = HtmlDomUtil.readFile(src.getParentFile(), src.getName());
+      String css =
+          HtmlDomUtil.readFile(src.getParent(), src.getFileName().toString());
       if (css == null) {
         return info;
       }
@@ -385,8 +385,8 @@
       return info;
     }
 
-    private FileInfo injectXmlFile(final Document hostDoc, final String id,
-        final File src) throws IOException {
+    private FileInfo injectXmlFile(Document hostDoc, String id, Path src)
+        throws IOException {
       final FileInfo info = new FileInfo(src);
       final Element banner = HtmlDomUtil.find(hostDoc, id);
       if (banner == null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
index 9c267a8..00568f0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
@@ -68,7 +68,7 @@
 
     CacheHeaders.setNotCacheable(rsp);
     rsp.setContentType("text/html");
-    rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+    rsp.setCharacterEncoding(HtmlDomUtil.ENC.name());
     rsp.setContentLength(tosend.length);
     final OutputStream out = rsp.getOutputStream();
     try {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
index 321f032..9c067de 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/template/SiteHeaderFooter.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.template;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.httpd.HtmlDomUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -27,8 +29,8 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 
 @Singleton
 public class SiteHeaderFooter {
@@ -43,13 +45,13 @@
     this.refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
     this.sitePaths = sitePaths;
 
-    Template t = new Template(sitePaths);
     try {
+      Template t = new Template(sitePaths);
       t.load();
+      template = t;
     } catch (IOException e) {
       log.warn("Cannot load site header or footer", e);
     }
-    template = t;
   }
 
   public Document parse(Class<?> clazz, String name) throws IOException {
@@ -118,8 +120,8 @@
 
     void load() throws IOException {
       css = HtmlDomUtil.readFile(
-          cssFile.path.getParentFile(),
-          cssFile.path.getName());
+          cssFile.path.getParent(),
+          cssFile.path.getFileName().toString());
       header = readXml(headerFile);
       footer = readXml(footerFile);
     }
@@ -135,16 +137,16 @@
   }
 
   private static class FileInfo {
-    final File path;
+    final Path path;
     final long time;
 
-    FileInfo(File p) {
+    FileInfo(Path p) {
       path = p;
-      time = path.lastModified();
+      time = lastModified(p);
     }
 
     boolean isStale() {
-      return time != path.lastModified();
+      return time != lastModified(path);
     }
   }
 }
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 4b34629..763b7ae 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -125,6 +125,7 @@
     ':init-api',
     ':pgm',
     '//gerrit-server:server',
+    '//lib:guava',
     '//lib:junit',
     '//lib/easymock:easymock',
     '//lib/guice:guice',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 7709b24..3f7d653 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.pgm;
 
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
@@ -92,10 +93,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.lang.Thread.UncaughtExceptionHandler;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -147,7 +148,7 @@
   private Injector sshInjector;
   private Injector webInjector;
   private Injector httpdInjector;
-  private File runFile;
+  private Path runFile;
   private boolean test;
   private AbstractModule luceneModule;
 
@@ -183,7 +184,7 @@
     });
 
     if (runId != null) {
-      runFile = new File(new File(getSitePath(), "logs"), "gerrit.run");
+      runFile = getSitePath().resolve("logs").resolve("gerrit.run");
     }
 
     if (httpd == null) {
@@ -207,7 +208,11 @@
         public void run() {
           log.info("caught shutdown, cleaning up");
           if (runId != null) {
-            runFile.delete();
+            try {
+              Files.delete(runFile);
+            } catch (IOException err) {
+              log.warn("failed to delete " + runFile, err);
+            }
           }
           manager.stop();
         }
@@ -216,15 +221,8 @@
       log.info("Gerrit Code Review " + myVersion() + " ready");
       if (runId != null) {
         try {
-          runFile.createNewFile();
-          runFile.setReadable(true, false);
-
-          FileOutputStream out = new FileOutputStream(runFile);
-          try {
-            out.write((runId + "\n").getBytes("UTF-8"));
-          } finally {
-            out.close();
-          }
+          Files.write(runFile, (runId + "\n").getBytes(UTF_8));
+          runFile.toFile().setReadable(true, false);
         } catch (IOException err) {
           log.warn("Cannot write --run-id to " + runFile, err);
         }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 29ab490..efc97f9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -37,8 +37,8 @@
 
 import org.kohsuke.args4j.Option;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -70,7 +70,7 @@
     super(new WarDistribution(), null);
   }
 
-  public Init(File sitePath) {
+  public Init(Path sitePath) {
     super(sitePath, true, true, new WarDistribution(), null);
     batchMode = true;
     noAutoStart = true;
@@ -106,7 +106,7 @@
     modules.add(new AbstractModule() {
       @Override
       protected void configure() {
-        bind(File.class).annotatedWith(SitePath.class).toInstance(getSitePath());
+        bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath());
         bind(Browser.class);
         bind(String.class).annotatedWith(SecureStoreClassName.class)
             .toProvider(Providers.of(getConfiguredSecureStoreClass()));
@@ -157,8 +157,8 @@
   }
 
   void startDaemon(SiteRun run) {
-    final String[] argv = {run.site.gerrit_sh.getAbsolutePath(), "start"};
-    final Process proc;
+    String[] argv = {run.site.gerrit_sh.toAbsolutePath().toString(), "start"};
+    Process proc;
     try {
       System.err.println("Executing " + argv[0] + " " + argv[1]);
       proc = Runtime.getRuntime().exec(argv);
@@ -177,7 +177,7 @@
 
     for (;;) {
       try {
-        final int rc = proc.waitFor();
+        int rc = proc.waitFor();
         if (rc != 0) {
           System.err.println("error: cannot start Gerrit: exit status " + rc);
         }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
index f2feae1..ac84e82 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -19,7 +19,6 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
-import com.google.common.io.Files;
 import com.google.gerrit.common.IoUtil;
 import com.google.gerrit.common.SiteLibraryLoaderUtil;
 import com.google.gerrit.pgm.util.SiteProgram;
@@ -37,8 +36,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.jar.JarFile;
@@ -47,7 +48,7 @@
 public class SwitchSecureStore extends SiteProgram {
   private static String getSecureStoreClassFromGerritConfig(SitePaths sitePaths) {
     FileBasedConfig cfg =
-        new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+        new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.DETECTED);
     try {
       cfg.load();
     } catch (IOException | ConfigInvalidException e) {
@@ -67,14 +68,14 @@
   @Override
   public int run() throws Exception {
     SitePaths sitePaths = new SitePaths(getSitePath());
-    File newSecureStoreFile = new File(newSecureStoreLib);
-    if (!newSecureStoreFile.exists()) {
-      log.error(String.format("File %s doesn't exists",
-          newSecureStoreFile.getAbsolutePath()));
+    Path newSecureStorePath = Paths.get(newSecureStoreLib);
+    if (!Files.exists(newSecureStorePath)) {
+      log.error(String.format("File %s doesn't exist",
+          newSecureStorePath.toAbsolutePath()));
       return -1;
     }
 
-    String newSecureStore = getNewSecureStoreClassName(newSecureStoreFile);
+    String newSecureStore = getNewSecureStoreClassName(newSecureStorePath);
     String currentSecureStoreName = getCurrentSecureStoreClassName(sitePaths);
 
     if (currentSecureStoreName.equals(newSecureStore)) {
@@ -83,7 +84,7 @@
       return -1;
     }
 
-    IoUtil.loadJARs(newSecureStoreFile);
+    IoUtil.loadJARs(newSecureStorePath);
     SiteLibraryLoaderUtil.loadSiteLib(sitePaths.lib_dir);
 
     log.info("Current secureStoreClass property ({}) will be replaced with {}",
@@ -96,7 +97,7 @@
     migrateProperties(currentStore, newStore);
 
     removeOldLib(sitePaths, currentSecureStoreName);
-    copyNewLib(sitePaths, newSecureStoreFile);
+    copyNewLib(sitePaths, newSecureStorePath);
 
     updateGerritConfig(sitePaths, newSecureStore);
 
@@ -123,14 +124,17 @@
     }
   }
 
-  private void removeOldLib(SitePaths sitePaths, String currentSecureStoreName) {
-    File oldSecureStore =
+  private void removeOldLib(SitePaths sitePaths, String currentSecureStoreName)
+      throws IOException {
+    Path oldSecureStore =
         findJarWithSecureStore(sitePaths, currentSecureStoreName);
     if (oldSecureStore != null) {
       log.info("Removing old SecureStore ({}) from lib/ directory",
-          oldSecureStore.getName());
-      if (!oldSecureStore.delete()) {
-        log.error("Cannot remove {}", oldSecureStore.getAbsolutePath());
+          oldSecureStore.getFileName());
+      try {
+        Files.delete(oldSecureStore);
+      } catch (IOException e) {
+        log.error("Cannot remove {}", oldSecureStore.toAbsolutePath(), e);
       }
     } else {
       log.info("Cannot find jar with old SecureStore ({}) in lib/ directory",
@@ -138,12 +142,12 @@
     }
   }
 
-  private void copyNewLib(SitePaths sitePaths, File newSecureStoreFile)
+  private void copyNewLib(SitePaths sitePaths, Path newSecureStorePath)
       throws IOException {
     log.info("Copy new SecureStore ({}) into lib/ directory",
-        newSecureStoreFile.getName());
-    Files.copy(newSecureStoreFile, new File(sitePaths.lib_dir,
-        newSecureStoreFile.getName()));
+        newSecureStorePath.getFileName());
+    Files.copy(newSecureStorePath,
+        sitePaths.lib_dir.resolve(newSecureStorePath.getFileName()));
   }
 
   private void updateGerritConfig(SitePaths sitePaths, String newSecureStore)
@@ -151,13 +155,13 @@
     log.info("Set gerrit.secureStoreClass property of gerrit.config to {}",
         newSecureStore);
     FileBasedConfig config =
-        new FileBasedConfig(sitePaths.gerrit_config, FS.DETECTED);
+        new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.DETECTED);
     config.load();
     config.setString("gerrit", null, "secureStoreClass", newSecureStore);
     config.save();
   }
 
-  private String getNewSecureStoreClassName(File secureStore)
+  private String getNewSecureStoreClassName(Path secureStore)
       throws IOException {
     JarScanner scanner = new JarScanner(secureStore);
     List<String> newSecureStores =
@@ -165,12 +169,12 @@
     if (newSecureStores.isEmpty()) {
       throw new RuntimeException(String.format(
           "Cannot find implementation of SecureStore interface in %s",
-          secureStore.getAbsolutePath()));
+          secureStore.toAbsolutePath()));
     }
     if (newSecureStores.size() > 1) {
       throw new RuntimeException(String.format(
           "Found too many implementations of SecureStore:\n%s\nin %s", Joiner
-              .on("\n").join(newSecureStores), secureStore.getAbsolutePath()));
+              .on("\n").join(newSecureStores), secureStore.toAbsolutePath()));
     }
     return Iterables.getOnlyElement(newSecureStores);
   }
@@ -195,15 +199,12 @@
     }
   }
 
-  private File findJarWithSecureStore(SitePaths sitePaths,
-      String secureStoreClass) {
-    File[] jars = SiteLibraryLoaderUtil.listJars(sitePaths.lib_dir);
-    if (jars == null || jars.length == 0) {
-      return null;
-    }
+  private Path findJarWithSecureStore(SitePaths sitePaths,
+      String secureStoreClass) throws IOException {
+    List<Path> jars = SiteLibraryLoaderUtil.listJars(sitePaths.lib_dir);
     String secureStoreClassPath = secureStoreClass.replace('.', '/') + ".class";
-    for (File jar : jars) {
-      try (JarFile jarFile = new JarFile(jar)) {
+    for (Path jar : jars) {
+      try (JarFile jarFile = new JarFile(jar.toFile())) {
         ZipEntry entry = jarFile.getEntry(secureStoreClassPath);
         if (entry != null) {
           return jar;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index c9e76c8..534ef050 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -56,9 +56,14 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -86,12 +91,12 @@
     this.pluginsToInstall = pluginsToInstall;
   }
 
-  public BaseInit(File sitePath, boolean standalone, boolean initDb,
+  public BaseInit(Path sitePath, boolean standalone, boolean initDb,
       PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
     this(sitePath, null, standalone, initDb, pluginsDistribution, pluginsToInstall);
   }
 
-  public BaseInit(File sitePath, final Provider<DataSource> dsProvider,
+  public BaseInit(Path sitePath, final Provider<DataSource> dsProvider,
       boolean standalone, boolean initDb,
       PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
     super(sitePath, dsProvider);
@@ -132,7 +137,7 @@
       throw failure;
     }
 
-    System.err.println("Initialized " + getSitePath().getCanonicalPath());
+    System.err.println("Initialized " + getSitePath().toRealPath().normalize());
     afterInit(run);
     return 0;
   }
@@ -208,7 +213,7 @@
 
   private SiteInit createSiteInit() {
     final ConsoleUI ui = getConsoleUI();
-    final File sitePath = getSitePath();
+    final Path sitePath = getSitePath();
     final List<Module> m = new ArrayList<>();
     final SecureStoreInitData secureStoreInitData = discoverSecureStoreClass();
     final String currentSecureStoreClassName = getConfiguredSecureStoreClass();
@@ -228,7 +233,7 @@
       @Override
       protected void configure() {
         bind(ConsoleUI.class).toInstance(ui);
-        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
         List<String> plugins =
             MoreObjects.firstNonNull(
                 getInstallPlugins(), Lists.<String> newArrayList());
@@ -287,8 +292,8 @@
     }
 
     try {
-      File secureStoreLib = new File(secureStore);
-      if (!secureStoreLib.exists()) {
+      Path secureStoreLib = Paths.get(secureStore);
+      if (!Files.exists(secureStoreLib)) {
         throw new InvalidSecureStoreException(String.format(
             "File %s doesn't exist", secureStore));
       }
@@ -408,15 +413,41 @@
     return sysInjector;
   }
 
-  private static void recursiveDelete(File path) {
-    File[] entries = path.listFiles();
-    if (entries != null) {
-      for (File e : entries) {
-        recursiveDelete(e);
-      }
-    }
-    if (!path.delete() && path.exists()) {
-      System.err.println("warn: Cannot remove " + path);
+  private static void recursiveDelete(Path path) {
+    final String msg = "warn: Cannot remove ";
+    try {
+      Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+        @Override
+        public FileVisitResult visitFile(Path f, BasicFileAttributes attrs)
+            throws IOException {
+          try {
+            Files.delete(f);
+          } catch (IOException e) {
+            System.err.println(msg + f);
+          }
+          return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path dir, IOException err) {
+          try {
+            // Previously warned if err was not null; if dir is not empty as a
+            // result, will cause an error that will be logged below.
+            Files.delete(dir);
+          } catch (IOException e) {
+            System.err.println(msg + dir);
+          }
+          return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult visitFileFailed(Path f, IOException e) {
+          System.err.println(msg + f);
+          return FileVisitResult.CONTINUE;
+        }
+      });
+    } catch (IOException e) {
+      System.err.println(msg + path);
     }
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index f830854..60ff665 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.pgm.init.api.InitUtil.die;
 import static com.google.gerrit.pgm.init.api.InitUtil.username;
 
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.launcher.GerritLauncher;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitStep;
@@ -28,11 +29,12 @@
 import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.util.FS;
 
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 /** Initialize the {@code container} configuration section. */
 @Singleton
@@ -56,9 +58,9 @@
     container.string("Run as", "user", username());
     container.string("Java runtime", "javaHome", javaHome());
 
-    File myWar;
+    Path myWar;
     try {
-      myWar = GerritLauncher.getDistributionArchive();
+      myWar = GerritLauncher.getDistributionArchive().toPath();
     } catch (FileNotFoundException e) {
       System.err.println("warn: Cannot find distribution archive (e.g. gerrit.war)");
       myWar = null;
@@ -66,53 +68,41 @@
 
     String path = container.get("war");
     if (path != null) {
-      path = container.string("Gerrit runtime", "war", //
-          myWar != null ? myWar.getAbsolutePath() : null);
+      path = container.string("Gerrit runtime", "war",
+          myWar != null ? myWar.toAbsolutePath().toString() : null);
       if (path == null || path.isEmpty()) {
         throw die("container.war is required");
       }
 
     } else if (myWar != null) {
       final boolean copy;
-      final File siteWar = site.gerrit_war;
-      if (siteWar.exists()) {
-        copy = ui.yesno(true, "Upgrade %s", siteWar.getPath());
+      final Path siteWar = site.gerrit_war;
+      if (Files.exists(siteWar)) {
+        copy = ui.yesno(true, "Upgrade %s", siteWar);
       } else {
-        copy = ui.yesno(true, "Copy %s to %s", myWar.getName(), siteWar.getPath());
+        copy = ui.yesno(true, "Copy %s to %s", myWar.getFileName(), siteWar);
         if (copy) {
           container.unset("war");
         } else {
-          container.set("war", myWar.getAbsolutePath());
+          container.set("war", myWar.toAbsolutePath().toString());
         }
       }
       if (copy) {
         if (!ui.isBatch()) {
-          System.err.format("Copying %s to %s", myWar.getName(), siteWar.getPath());
+          System.err.format("Copying %s to %s", myWar.getFileName(), siteWar);
           System.err.println();
         }
 
-        FileInputStream in = new FileInputStream(myWar);
-        try {
-          siteWar.getParentFile().mkdirs();
+        try (InputStream in = Files.newInputStream(myWar)) {
+          Files.createDirectories(siteWar.getParent());
 
-          LockFile lf = new LockFile(siteWar, FS.DETECTED);
+          LockFile lf = new LockFile(siteWar.toFile(), FS.DETECTED);
           if (!lf.lock()) {
             throw new IOException("Cannot lock " + siteWar);
           }
-
           try {
-            final OutputStream out = lf.getOutputStream();
-            try {
-              final byte[] tmp = new byte[4096];
-              for (;;) {
-                int n = in.read(tmp);
-                if (n < 0) {
-                  break;
-                }
-                out.write(tmp, 0, n);
-              }
-            } finally {
-              out.close();
+            try (OutputStream out = lf.getOutputStream()) {
+              ByteStreams.copy(in, out);
             }
             if (!lf.commit()) {
               throw new IOException("Cannot commit " + siteWar);
@@ -120,8 +110,6 @@
           } finally {
             lf.unlock();
           }
-        } finally {
-          in.close();
         }
       }
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index c8f1cd7..bf373ea 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -29,10 +29,11 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 /** Initialize the {@code httpd} configuration section. */
 @Singleton
@@ -149,8 +150,9 @@
       return;
     }
 
-    final File store = site.ssl_keystore;
-    if (!ui.yesno(!store.exists(), "Create new self-signed SSL certificate")) {
+    Path store = site.ssl_keystore;
+    if (!ui.yesno(!Files.exists(store),
+        "Create new self-signed SSL certificate")) {
       return;
     }
 
@@ -167,15 +169,17 @@
     final String dname =
         "CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname);
 
-    final File tmpdir = new File(site.etc_dir, "tmp.sslcertgen");
-    if (!tmpdir.mkdir()) {
-      throw die("Cannot create directory " + tmpdir);
+    Path tmpdir = site.etc_dir.resolve("tmp.sslcertgen");
+    try {
+      Files.createDirectory(tmpdir);
+    } catch (IOException e) {
+      throw die("Cannot create directory " + tmpdir, e);
     }
     chmod(0600, tmpdir);
 
-    final File tmpstore = new File(tmpdir, "keystore");
+    Path tmpstore = tmpdir.resolve("keystore");
     Runtime.getRuntime().exec(new String[] {"keytool", //
-        "-keystore", tmpstore.getAbsolutePath(), //
+        "-keystore", tmpstore.toAbsolutePath().toString(), //
         "-storepass", ssl_pass, //
         "-genkeypair", //
         "-alias", hostname, //
@@ -186,11 +190,15 @@
     }).waitFor();
     chmod(0600, tmpstore);
 
-    if (!tmpstore.renameTo(store)) {
-      throw die("Cannot rename " + tmpstore + " to " + store);
+    try {
+      Files.move(tmpstore, store);
+    } catch (IOException e) {
+      throw die("Cannot rename " + tmpstore + " to " + store, e);
     }
-    if (!tmpdir.delete()) {
-      throw die("Cannot delete " + tmpdir);
+    try {
+      Files.delete(tmpdir);
+    } catch (IOException e) {
+      throw die("Cannot delete " + tmpdir, e);
     }
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 893f00d..2cffe42 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.pgm.init;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.Ordering;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitStep;
@@ -26,23 +27,22 @@
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
 
-import java.io.File;
-import java.io.FileFilter;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
 
 @Singleton
 public class InitPluginStepsLoader {
-  private final File pluginsDir;
+  private final Path pluginsDir;
   private final Injector initInjector;
   final ConsoleUI ui;
 
@@ -55,10 +55,10 @@
   }
 
   public Collection<InitStep> getInitSteps() {
-    List<File> jars = scanJarsInPluginsDirectory();
+    List<Path> jars = scanJarsInPluginsDirectory();
     ArrayList<InitStep> pluginsInitSteps = new ArrayList<>();
 
-    for (File jar : jars) {
+    for (Path jar : jars) {
       InitStep init = loadInitStep(jar);
       if (init != null) {
         pluginsInitSteps.add(init);
@@ -68,12 +68,12 @@
   }
 
   @SuppressWarnings("resource")
-  private InitStep loadInitStep(File jar) {
+  private InitStep loadInitStep(Path jar) {
     try {
       URLClassLoader pluginLoader =
-          new URLClassLoader(new URL[] {jar.toURI().toURL()},
+          new URLClassLoader(new URL[] {jar.toUri().toURL()},
              InitPluginStepsLoader.class.getClassLoader());
-      try (JarFile jarFile = new JarFile(jar)) {
+      try (JarFile jarFile = new JarFile(jar.toFile())) {
         Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
         String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
         if (initClassName == null) {
@@ -86,7 +86,7 @@
       } catch (ClassCastException e) {
         ui.message(
             "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
-            jar.getName(), InitStep.class.getName(), e.getMessage());
+            jar.getFileName(), InitStep.class.getName(), e.getMessage());
         return null;
       }
     } catch (Exception e) {
@@ -97,11 +97,10 @@
     }
   }
 
-  private Injector getPluginInjector(final File jarFile) throws IOException {
-    final String pluginName =
-        MoreObjects.firstNonNull(
-            JarPluginProvider.getJarPluginName(jarFile),
-            PluginLoader.nameOf(jarFile));
+  private Injector getPluginInjector(Path jarPath) throws IOException {
+    final String pluginName = MoreObjects.firstNonNull(
+        JarPluginProvider.getJarPluginName(jarPath),
+        PluginLoader.nameOf(jarPath));
     return initInjector.createChildInjector(new AbstractModule() {
       @Override
       protected void configure() {
@@ -111,27 +110,24 @@
     });
   }
 
-  private List<File> scanJarsInPluginsDirectory() {
-    if (pluginsDir == null || !pluginsDir.exists()) {
+  private List<Path> scanJarsInPluginsDirectory() {
+    if (pluginsDir == null || !Files.isDirectory(pluginsDir)) {
       return Collections.emptyList();
     }
-    File[] matches = pluginsDir.listFiles(new FileFilter() {
+    DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
       @Override
-      public boolean accept(File pathname) {
-        String n = pathname.getName();
-        return (n.endsWith(".jar") && pathname.isFile());
+      public boolean accept(Path entry) throws IOException {
+        return entry.getFileName().toString().endsWith(".jar")
+            && Files.isRegularFile(entry);
       }
-    });
-    if (matches == null) {
-      ui.message("WARN: Cannot list %s", pluginsDir.getAbsolutePath());
+    };
+    try (DirectoryStream<Path> paths =
+        Files.newDirectoryStream(pluginsDir, filter)) {
+      return Ordering.natural().sortedCopy(paths);
+    } catch (IOException e) {
+      ui.message("WARN: Cannot list %s: %s", pluginsDir.toAbsolutePath(),
+          e.getMessage());
       return Collections.emptyList();
     }
-    Arrays.sort(matches, new Comparator<File>() {
-      @Override
-      public int compare(File o1, File o2) {
-        return o1.getName().compareTo(o2.getName());
-      }
-    });
-    return Arrays.asList(matches);
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index cc076b5..ca4b949 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -25,9 +25,10 @@
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
@@ -55,10 +56,10 @@
     pluginsDistribution.foreach(new PluginsDistribution.Processor() {
       @Override
       public void process(String pluginName, InputStream in) throws IOException {
-        File tmpPlugin = JarPluginProvider.storeInTemp(pluginName, in, site);
+        Path tmpPlugin = JarPluginProvider.storeInTemp(pluginName, in, site);
         String pluginVersion = getVersion(tmpPlugin);
         if (deleteTempPluginFile) {
-          tmpPlugin.delete();
+          Files.delete(tmpPlugin);
         }
         result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
       }
@@ -108,37 +109,39 @@
     for (PluginData plugin : plugins) {
       String pluginName = plugin.name;
       try {
-        final File tmpPlugin = plugin.pluginFile;
+        final Path tmpPlugin = plugin.pluginPath;
 
         if (!(initFlags.installPlugins.contains(pluginName) || ui.yesno(false,
             "Install plugin %s version %s", pluginName, plugin.version))) {
-          tmpPlugin.delete();
+          Files.deleteIfExists(tmpPlugin);
           continue;
         }
 
-        final File p = new File(site.plugins_dir, plugin.name + ".jar");
-        if (p.exists()) {
+        final Path p = site.plugins_dir.resolve(plugin.name + ".jar");
+        if (Files.exists(p)) {
           final String installedPluginVersion = getVersion(p);
           if (!ui.yesno(false,
               "version %s is already installed, overwrite it",
               installedPluginVersion)) {
-            tmpPlugin.delete();
+            Files.deleteIfExists(tmpPlugin);
             continue;
           }
-          if (!p.delete()) {
+          try {
+            Files.delete(p);
+          } catch (IOException e) {
             throw new IOException("Failed to delete plugin " + pluginName
-                + ": " + p.getAbsolutePath());
+                + ": " + p.toAbsolutePath(), e);
           }
         }
-        if (!tmpPlugin.renameTo(p)) {
+        try {
+          Files.move(tmpPlugin, p);
+        } catch (IOException e) {
           throw new IOException("Failed to install plugin " + pluginName
-              + ": " + tmpPlugin.getAbsolutePath() + " -> "
-              + p.getAbsolutePath());
+              + ": " + tmpPlugin.toAbsolutePath() + " -> "
+              + p.toAbsolutePath(), e);
         }
       } finally {
-        if (plugin.pluginFile.exists()) {
-          plugin.pluginFile.delete();
-        }
+        Files.deleteIfExists(plugin.pluginPath);
       }
     }
     if (plugins.isEmpty()) {
@@ -159,11 +162,11 @@
     }
   }
 
-  private static String getVersion(final File plugin) throws IOException {
-    final JarFile jarFile = new JarFile(plugin);
+  private static String getVersion(Path plugin) throws IOException {
+    JarFile jarFile = new JarFile(plugin.toFile());
     try {
-      final Manifest manifest = jarFile.getManifest();
-      final Attributes main = manifest.getMainAttributes();
+      Manifest manifest = jarFile.getManifest();
+      Attributes main = manifest.getMainAttributes();
       return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
     } finally {
       jarFile.close();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index 51eaa22..5c7eefd 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -25,6 +25,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import java.nio.file.Files;
+
 /** Initialize the {@code sendemail} configuration section. */
 @Singleton
 class InitSendEmail implements InitStep {
@@ -54,7 +56,7 @@
             true);
 
     String username = null;
-    if (site.gerrit_config.exists()) {
+    if (Files.exists(site.gerrit_config)) {
       username = sendemail.get("smtpUser");
     } else if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) {
       username = username();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index ed18d73..c654c8d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.common.FileUtil.chmod;
 import static com.google.gerrit.pgm.init.api.InitUtil.die;
 import static com.google.gerrit.pgm.init.api.InitUtil.hostname;
+import static java.nio.file.Files.exists;
 
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitStep;
@@ -29,9 +30,10 @@
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 /** Initialize the {@code sshd} configuration section. */
 @Singleton
@@ -74,9 +76,9 @@
     port = ui.readInt(port, "Listen on port");
     sshd.set("listenAddress", SocketUtil.format(hostname, port));
 
-    if (site.ssh_rsa.exists() || site.ssh_dsa.exists()) {
+    if (exists(site.ssh_rsa) || exists(site.ssh_dsa)) {
       libraries.bouncyCastleSSL.downloadRequired();
-    } else if (!site.ssh_key.exists()) {
+    } else if (!exists(site.ssh_key)) {
       libraries.bouncyCastleSSL.downloadOptional();
     }
 
@@ -90,9 +92,9 @@
   }
 
   private void generateSshHostKeys() throws InterruptedException, IOException {
-    if (!site.ssh_key.exists() //
-        && !site.ssh_rsa.exists() //
-        && !site.ssh_dsa.exists()) {
+    if (!exists(site.ssh_key) //
+        && !exists(site.ssh_rsa) //
+        && !exists(site.ssh_dsa)) {
       System.err.print("Generating SSH host key ...");
       System.err.flush();
 
@@ -108,7 +110,7 @@
             "-t", "rsa", //
             "-P", "", //
             "-C", comment, //
-            "-f", site.ssh_rsa.getAbsolutePath() //
+            "-f", site.ssh_rsa.toAbsolutePath().toString() //
             }).waitFor();
 
         System.err.print(" dsa...");
@@ -118,7 +120,7 @@
             "-t", "dsa", //
             "-P", "", //
             "-C", comment, //
-            "-f", site.ssh_dsa.getAbsolutePath() //
+            "-f", site.ssh_dsa.toAbsolutePath().toString() //
             }).waitFor();
 
       } else {
@@ -128,28 +130,34 @@
         // short period of time. We try to reduce that risk by creating
         // the key within a temporary directory.
         //
-        final File tmpdir = new File(site.etc_dir, "tmp.sshkeygen");
-        if (!tmpdir.mkdir()) {
-          throw die("Cannot create directory " + tmpdir);
+        Path tmpdir = site.etc_dir.resolve("tmp.sshkeygen");
+        try {
+          Files.createDirectory(tmpdir);
+        } catch (IOException e) {
+          throw die("Cannot create directory " + tmpdir, e);
         }
         chmod(0600, tmpdir);
 
-        final File tmpkey = new File(tmpdir, site.ssh_key.getName());
-        final SimpleGeneratorHostKeyProvider p;
+        Path tmpkey = tmpdir.resolve(site.ssh_key.getFileName().toString());
+        SimpleGeneratorHostKeyProvider p;
 
         System.err.print(" rsa(simple)...");
         System.err.flush();
         p = new SimpleGeneratorHostKeyProvider();
-        p.setPath(tmpkey.getAbsolutePath());
+        p.setPath(tmpkey.toAbsolutePath().toString());
         p.setAlgorithm("RSA");
         p.loadKeys(); // forces the key to generate.
         chmod(0600, tmpkey);
 
-        if (!tmpkey.renameTo(site.ssh_key)) {
-          throw die("Cannot rename " + tmpkey + " to " + site.ssh_key);
+        try {
+          Files.move(tmpkey, site.ssh_key);
+        } catch (IOException e) {
+          throw die("Cannot rename " + tmpkey + " to " + site.ssh_key, e);
         }
-        if (!tmpdir.delete()) {
-          throw die("Cannot delete " + tmpdir);
+        try {
+          Files.delete(tmpdir);
+        } catch (IOException e) {
+          throw die("Cannot delete " + tmpdir, e);
         }
       }
       System.err.println(" done");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index 4bf1c88..5863d48 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -15,21 +15,19 @@
 package com.google.gerrit.pgm.init;
 
 import com.google.common.base.Strings;
-import com.google.common.io.Files;
+import com.google.common.hash.Funnels;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.common.IoUtil;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.util.HttpSupport;
 
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -38,15 +36,17 @@
 import java.net.ProxySelector;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 
 /** Get optional or required 3rd party library files into $site_path/lib. */
 class LibraryDownloader {
   private final ConsoleUI ui;
-  private final File lib_dir;
+  private final Path lib_dir;
 
   private boolean required;
   private String name;
@@ -55,7 +55,7 @@
   private String remove;
   private List<LibraryDownloader> needs;
   private LibraryDownloader neededBy;
-  private File dst;
+  private Path dst;
   private boolean download; // download or copy
   private boolean exists;
 
@@ -118,8 +118,8 @@
       name = jarName;
     }
 
-    dst = new File(lib_dir, jarName);
-    if (dst.exists()) {
+    dst = lib_dir.resolve(jarName);
+    if (Files.exists(dst)) {
       exists = true;
     } else if (shouldGet()) {
       doGet();
@@ -158,8 +158,12 @@
   }
 
   private void doGet() {
-    if (!lib_dir.exists() && !lib_dir.mkdirs()) {
-      throw new Die("Cannot create " + lib_dir);
+    if (!Files.exists(lib_dir)) {
+      try {
+        Files.createDirectories(lib_dir);
+      } catch (IOException e) {
+        throw new Die("Cannot create " + lib_dir, e);
+      }
     }
 
     try {
@@ -171,7 +175,11 @@
       }
       verifyFileChecksum();
     } catch (IOException err) {
-      dst.delete();
+      try {
+        Files.delete(dst);
+      } catch (IOException e) {
+        // Delete failed; leave alone.
+      }
 
       if (ui.isBatch()) {
         throw new Die("error: Cannot get " + jarUrl, err);
@@ -186,13 +194,13 @@
       System.err.println();
       System.err.println("and save as:");
       System.err.println();
-      System.err.println("  " + dst.getAbsolutePath());
+      System.err.println("  " + dst.toAbsolutePath());
       System.err.println();
       System.err.flush();
 
       ui.waitForUser();
 
-      if (dst.exists()) {
+      if (Files.exists(dst)) {
         verifyFileChecksum();
 
       } else if (!ui.yesno(!required, "Continue without this library")) {
@@ -200,7 +208,7 @@
       }
     }
 
-    if (dst.exists()) {
+    if (Files.exists(dst)) {
       exists = true;
       IoUtil.loadJARs(dst);
     }
@@ -208,19 +216,27 @@
 
   private void removeStaleVersions() {
     if (!Strings.isNullOrEmpty(remove)) {
-      String[] names = lib_dir.list(new FilenameFilter() {
-        @Override
-        public boolean accept(File dir, String name) {
-          return name.matches("^" + remove + "$");
-        }
-      });
-      if (names != null) {
-        for (String old : names) {
-          String bak = "." + old + ".backup";
-          ui.message("Renaming %s to %s", old, bak);
-          if (!new File(lib_dir, old).renameTo(new File(lib_dir, bak))) {
-            throw new Die("cannot rename " + old);
-          }
+      Iterable<Path> paths;
+      try {
+        paths = Files.newDirectoryStream(lib_dir,
+            new DirectoryStream.Filter<Path>() {
+              @Override
+              public boolean accept(Path entry) {
+                return entry.getFileName().toString()
+                    .matches("^" + remove + "$");
+              }
+            });
+      } catch (IOException e) {
+        throw new Die("cannot remove stale library versions", e);
+      }
+      for (Path p : paths) {
+        String old = p.getFileName().toString();
+        String bak = "." + old + ".backup";
+        ui.message("Renaming %s to %s", old, bak);
+        try {
+          Files.move(p, p.resolveSibling(bak));
+        } catch (IOException e) {
+          throw new Die("cannot rename " + old, e);
         }
       }
     }
@@ -228,111 +244,93 @@
 
   private void doGetByLocalCopy() throws IOException {
     System.err.print("Copying " + jarUrl + " ...");
-    File f = url2file(jarUrl);
-    if (!f.exists()) {
+    Path p = url2file(jarUrl);
+    if (!Files.exists(p)) {
       StringBuilder msg = new StringBuilder()
           .append("\n")
           .append("Can not find the %s at this location: %s\n")
           .append("Please provide alternative URL");
-      f = url2file(ui.readString(null, msg.toString(), name, jarUrl));
+      p = url2file(ui.readString(null, msg.toString(), name, jarUrl));
     }
-    Files.copy(f, dst);
+    Files.copy(p, dst);
   }
 
-  private static File url2file(final String urlString) throws IOException {
+  private static Path url2file(final String urlString) throws IOException {
     final URL url = new URL(urlString);
     try {
-      return new File(url.toURI());
+      return Paths.get(url.toURI());
     } catch (URISyntaxException e) {
-      return new File(url.getPath());
+      return Paths.get(url.getPath());
     }
   }
 
   private void doGetByHttp() throws IOException {
     System.err.print("Downloading " + jarUrl + " ...");
     System.err.flush();
-    try {
-      final ProxySelector proxySelector = ProxySelector.getDefault();
-      final URL url = new URL(jarUrl);
-      final Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
-      final HttpURLConnection c = (HttpURLConnection) url.openConnection(proxy);
-      final InputStream in;
-
-      switch (HttpSupport.response(c)) {
-        case HttpURLConnection.HTTP_OK:
-          in = c.getInputStream();
-          break;
-
-        case HttpURLConnection.HTTP_NOT_FOUND:
-          throw new FileNotFoundException(url.toString());
-
-        default:
-          throw new IOException(url.toString() + ": " + HttpSupport.response(c)
-              + " " + c.getResponseMessage());
-      }
-
-      try {
-        final OutputStream out = new FileOutputStream(dst);
-        try {
-          final byte[] buf = new byte[8192];
-          int n;
-          while ((n = in.read(buf)) > 0) {
-            out.write(buf, 0, n);
-          }
-        } finally {
-          out.close();
-        }
-      } finally {
-        in.close();
-      }
+    try (InputStream in = openHttpStream(jarUrl);
+        OutputStream out = Files.newOutputStream(dst)) {
+      ByteStreams.copy(in, out);
       System.err.println(" OK");
       System.err.flush();
     } catch (IOException err) {
-      dst.delete();
+      deleteDst();
       System.err.println(" !! FAIL !!");
       System.err.flush();
       throw err;
     }
   }
 
+  private static InputStream openHttpStream(String urlStr) throws IOException {
+    ProxySelector proxySelector = ProxySelector.getDefault();
+    URL url = new URL(urlStr);
+    Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
+    HttpURLConnection c = (HttpURLConnection) url.openConnection(proxy);
+
+    switch (HttpSupport.response(c)) {
+      case HttpURLConnection.HTTP_OK:
+        return c.getInputStream();
+
+      case HttpURLConnection.HTTP_NOT_FOUND:
+        throw new FileNotFoundException(url.toString());
+
+      default:
+        throw new IOException(url.toString() + ": " + HttpSupport.response(c)
+            + " " + c.getResponseMessage());
+    }
+  }
+
   private void verifyFileChecksum() {
-    if (sha1 != null) {
-      try {
-        final MessageDigest md = MessageDigest.getInstance("SHA-1");
-        final FileInputStream in = new FileInputStream(dst);
-        try {
-          final byte[] buf = new byte[8192];
-          int n;
-          while ((n = in.read(buf)) > 0) {
-            md.update(buf, 0, n);
-          }
-        } finally {
-          in.close();
-        }
+    if (sha1 == null) {
+      return;
+    }
+    Hasher h = Hashing.sha1().newHasher();
+    try (InputStream in = Files.newInputStream(dst);
+        OutputStream out = Funnels.asOutputStream(h)) {
+      ByteStreams.copy(in, out);
+    } catch (IOException e) {
+      deleteDst();
+      throw new Die("cannot checksum " + dst, e);
+    }
+    if (sha1.equals(h.hash().toString())) {
+      System.err.println("Checksum " + dst.getFileName() + " OK");
+      System.err.flush();
+    } else if (ui.isBatch()) {
+      deleteDst();
+      throw new Die(dst + " SHA-1 checksum does not match");
 
-        if (sha1.equals(ObjectId.fromRaw(md.digest()).name())) {
-          System.err.println("Checksum " + dst.getName() + " OK");
-          System.err.flush();
+    } else if (!ui.yesno(null /* force an answer */,
+        "error: SHA-1 checksum does not match\n" + "Use %s anyway",//
+        dst.getFileName())) {
+      deleteDst();
+      throw new Die("aborted by user");
+    }
+  }
 
-        } else if (ui.isBatch()) {
-          dst.delete();
-          throw new Die(dst + " SHA-1 checksum does not match");
-
-        } else if (!ui.yesno(null /* force an answer */,
-            "error: SHA-1 checksum does not match\n" + "Use %s anyway",//
-            dst.getName())) {
-          dst.delete();
-          throw new Die("aborted by user");
-        }
-
-      } catch (IOException checksumError) {
-        dst.delete();
-        throw new Die("cannot checksum " + dst, checksumError);
-
-      } catch (NoSuchAlgorithmException checksumError) {
-        dst.delete();
-        throw new Die("cannot checksum " + dst, checksumError);
-      }
+  private void deleteDst() {
+    try {
+      Files.delete(dst);
+    } catch (IOException e) {
+      System.err.println(" Failed to clean up lib: " + dst);
     }
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java
index 8926759..49877dc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SecureStoreInitData.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.pgm.init;
 
-import java.io.File;
+import java.nio.file.Path;
 
 class SecureStoreInitData {
-  final File jarFile;
+  final Path jarFile;
   final String className;
 
-  SecureStoreInitData(File jar, String className) {
+  SecureStoreInitData(Path jar, String className) {
     this.className = className;
     this.jarFile = jar;
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 10c9bad..f867829 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -21,7 +21,6 @@
 import static com.google.gerrit.pgm.init.api.InitUtil.savePublic;
 import static com.google.gerrit.pgm.init.api.InitUtil.version;
 
-import com.google.common.io.Files;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
@@ -37,6 +36,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -132,7 +133,8 @@
 
   private void saveSecureStore() throws IOException {
     if (secureStoreInitData != null) {
-      File dst = new File(site.lib_dir, secureStoreInitData.jarFile.getName());
+      Path dst =
+          site.lib_dir.resolve(secureStoreInitData.jarFile.getFileName());
       Files.copy(secureStoreInitData.jarFile, dst);
       Section gerritSection = sectionFactory.get("gerrit", null);
       gerritSection.set("secureStoreClass", secureStoreInitData.className);
@@ -141,7 +143,7 @@
 
   private void extractMailExample(String orig) throws Exception {
     File ex = new File(site.mail_dir, orig + ".example");
-    extract(ex, OutgoingEmail.class, orig);
+    extract(ex.toPath(), OutgoingEmail.class, orig);
     chmod(0444, ex);
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index 8c13540..46f87ee 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -37,6 +37,8 @@
 import java.io.UnsupportedEncodingException;
 import java.net.InetSocketAddress;
 import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.Properties;
@@ -65,7 +67,7 @@
   private final FileBasedConfig cfg;
   private final SecureStore sec;
   private final File site_path;
-  private final File etc_dir;
+  private final Path etc_dir;
   private final Section.Factory sections;
 
   @Inject
@@ -100,14 +102,16 @@
     }
 
     for (String name : etcFiles) {
-      final File src = new File(site_path, name);
-      final File dst = new File(etc_dir, name);
-      if (src.exists()) {
-        if (dst.exists()) {
+      Path src = site_path.toPath().resolve(name);
+      Path dst = etc_dir.resolve(name);
+      if (Files.exists(src)) {
+        if (Files.exists(dst)) {
           throw die("File " + src + " would overwrite " + dst);
         }
-        if (!src.renameTo(dst)) {
-          throw die("Cannot rename " + src + " to " + dst);
+        try {
+          Files.move(src, dst);
+        } catch (IOException e) {
+          throw die("Cannot rename " + src + " to " + dst, e);
         }
       }
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
index 2a8155e..07137bc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitFlags.java
@@ -51,7 +51,7 @@
       ConfigInvalidException {
     sec = secureStore;
     this.installPlugins = installPlugins;
-    cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+    cfg = new FileBasedConfig(site.gerrit_config.toFile(), FS.DETECTED);
 
     cfg.load();
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java
index 881208d..904af2f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitUtil.java
@@ -16,14 +16,15 @@
 
 import static com.google.gerrit.common.FileUtil.modified;
 
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.common.Die;
 
 import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.SystemReader;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -33,7 +34,10 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Arrays;
 
 /** Utility functions to help initialize a site. */
 public class InitUtil {
@@ -51,9 +55,18 @@
     }
   }
 
-  public static void mkdir(final File path) {
-    if (!path.isDirectory() && !path.mkdir()) {
-      throw die("Cannot make directory " + path);
+  public static void mkdir(File file) {
+    mkdir(file.toPath());
+  }
+
+  public static void mkdir(Path path) {
+    if (Files.isDirectory(path)) {
+      return;
+    }
+    try {
+      Files.createDirectory(path);
+    } catch (IOException e) {
+      throw die("Cannot make directory " + path, e);
     }
   }
 
@@ -109,12 +122,11 @@
     return name;
   }
 
-  public static void extract(final File dst, final Class<?> sibling,
-      final String name) throws IOException {
+  public static void extract(Path dst, Class<?> sibling, String name)
+      throws IOException {
     try (InputStream in = open(sibling, name)) {
       if (in != null) {
-        ByteBuffer buf = IO.readWholeStream(in, 8192);
-        copy(dst, buf);
+        copy(dst, ByteStreams.toByteArray(in));
       }
     }
   }
@@ -136,35 +148,28 @@
     return in;
   }
 
-  public static void copy(final File dst, final ByteBuffer buf)
+  public static void copy(Path dst, byte[] buf)
       throws FileNotFoundException, IOException {
     // If the file already has the content we want to put there,
     // don't attempt to overwrite the file.
     //
-    try {
-      if (buf.equals(ByteBuffer.wrap(IO.readFully(dst)))) {
+    try (InputStream in = Files.newInputStream(dst)) {
+      if (Arrays.equals(buf, ByteStreams.toByteArray(in))) {
         return;
       }
-    } catch (FileNotFoundException notFound) {
+    } catch (NoSuchFileException notFound) {
       // Fall through and write the file.
     }
 
-    dst.getParentFile().mkdirs();
-    LockFile lf = new LockFile(dst, FS.DETECTED);
+    Files.createDirectories(dst.getParent());
+    LockFile lf = new LockFile(dst.toFile(), FS.DETECTED);
     if (!lf.lock()) {
       throw new IOException("Cannot lock " + dst);
     }
     try {
-      final OutputStream out = lf.getOutputStream();
-      try {
-        final byte[] tmp = new byte[4096];
-        while (0 < buf.remaining()) {
-          int n = Math.min(buf.remaining(), tmp.length);
-          buf.get(tmp, 0, n);
-          out.write(tmp, 0, n);
-        }
-      } finally {
-        out.close();
+      try (InputStream in = new ByteArrayInputStream(buf);
+          OutputStream out = lf.getOutputStream()) {
+        ByteStreams.copy(in, out);
       }
       if (!lf.commit()) {
         throw new IOException("Cannot commit " + dst);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
index a766d1e..3b1714e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.pgm.util;
 
-import com.google.gerrit.common.Die;
+import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.SystemLog;
@@ -25,8 +25,8 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
 
-import java.io.File;
 import java.io.FileNotFoundException;
+import java.nio.file.Path;
 
 public class ErrorLogFile {
   static final String LOG_NAME = "error_log";
@@ -47,12 +47,10 @@
     root.addAppender(dst);
   }
 
-  public static LifecycleListener start(final File sitePath)
+  public static LifecycleListener start(final Path sitePath)
       throws FileNotFoundException {
-    final File logdir = new SitePaths(sitePath).logs_dir;
-    if (!logdir.exists() && !logdir.mkdirs()) {
-      throw new Die("Cannot create log directory: " + logdir);
-    }
+    Path logdir = FileUtil.mkdirsOrDie(new SitePaths(sitePath).logs_dir,
+        "Cannot create log directory");
     if (SystemLog.shouldConfigure()) {
       initLogSystem(logdir);
     }
@@ -69,7 +67,7 @@
     };
   }
 
-  private static void initLogSystem(final File logdir) {
+  private static void initLogSystem(Path logdir) {
     final Logger root = LogManager.getRootLogger();
     root.removeAllAppenders();
     root.addAppender(SystemLog.createAppender(logdir, LOG_NAME,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
index 7d33a36..3d2b8e1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.pgm.util;
 
-import com.google.gerrit.common.Die;
+import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GarbageCollection;
@@ -24,17 +24,14 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
 
-import java.io.File;
 import java.io.FileNotFoundException;
+import java.nio.file.Path;
 
 public class GarbageCollectionLogFile {
-
-  public static LifecycleListener start(File sitePath)
+  public static LifecycleListener start(Path sitePath)
       throws FileNotFoundException {
-    File logdir = new SitePaths(sitePath).logs_dir;
-    if (!logdir.exists() && !logdir.mkdirs()) {
-      throw new Die("Cannot create log directory: " + logdir);
-    }
+    Path logdir = FileUtil.mkdirsOrDie(new SitePaths(sitePath).logs_dir,
+        "Cannot create log directory");
     if (SystemLog.shouldConfigure()) {
       initLogSystem(logdir);
     }
@@ -51,7 +48,7 @@
     };
   }
 
-  private static void initLogSystem(File logdir) {
+  private static void initLogSystem(Path logdir) {
     Logger gcLogger = LogManager.getLogger(GarbageCollection.LOG_NAME);
     gcLogger.removeAllAppenders();
     gcLogger.addAppender(SystemLog.createAppender(logdir,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
index db74ac3..1107208 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
@@ -16,6 +16,7 @@
 
 import static java.util.concurrent.TimeUnit.HOURS;
 
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
@@ -25,12 +26,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.zip.GZIPOutputStream;
 
 /** Compresses the old error logs. */
@@ -65,76 +66,78 @@
     }
   }
 
-  private final File logs_dir;
+  private final Path logs_dir;
 
   @Inject
   LogFileCompressor(final SitePaths site) {
     logs_dir = resolve(site.logs_dir);
   }
 
-  private static File resolve(final File logs_dir) {
+  private static Path resolve(Path p) {
     try {
-      return logs_dir.getCanonicalFile();
+      return p.toRealPath().normalize();
     } catch (IOException e) {
-      return logs_dir.getAbsoluteFile();
+      return p.toAbsolutePath().normalize();
     }
   }
 
   @Override
   public void run() {
-    final File[] list = logs_dir.listFiles();
-    if (list == null) {
+    if (!Files.isDirectory(logs_dir)) {
       return;
     }
-
-    for (final File entry : list) {
-      if (!isLive(entry) && !isCompressed(entry) && isLogFile(entry)) {
-        compress(entry);
+    try (DirectoryStream<Path> list = Files.newDirectoryStream(logs_dir)) {
+      for (Path entry : list) {
+        if (!isLive(entry) && !isCompressed(entry) && isLogFile(entry)) {
+          compress(entry);
+        }
       }
+    } catch (IOException e) {
+      log.error("Error listing logs to compress in " + logs_dir, e);
     }
   }
 
-  private boolean isLive(final File entry) {
-    final String name = entry.getName();
+  private boolean isLive(Path entry) {
+    String name = entry.getFileName().toString();
     return name.endsWith("_log")
         || name.endsWith(".log")
         || name.endsWith(".run")
         || name.endsWith(".pid");
   }
 
-  private boolean isCompressed(final File entry) {
-    final String name = entry.getName();
+  private boolean isCompressed(Path entry) {
+    String name = entry.getFileName().toString();
     return name.endsWith(".gz") //
         || name.endsWith(".zip") //
         || name.endsWith(".bz2");
   }
 
-  private boolean isLogFile(final File entry) {
-    return entry.isFile();
+  private boolean isLogFile(Path entry) {
+    return Files.isRegularFile(entry);
   }
 
-  private void compress(final File src) {
-    final File dir = src.getParentFile();
-    final File dst = new File(dir, src.getName() + ".gz");
-    final File tmp = new File(dir, ".tmp." + src.getName());
+  private void compress(Path src) {
+    Path dst = src.resolveSibling(src.getFileName() + ".gz");
+    Path tmp = src.resolveSibling(".tmp." + src.getFileName());
     try {
-      try (InputStream in = new FileInputStream(src);
-          FileOutputStream fo = new FileOutputStream(tmp);
-          OutputStream out = new GZIPOutputStream(fo)) {
-        final byte[] buf = new byte[2048];
-        int n;
-        while (0 < (n = in.read(buf))) {
-          out.write(buf, 0, n);
-        }
-        tmp.setReadOnly();
+      try (InputStream in = Files.newInputStream(src);
+          OutputStream out = new GZIPOutputStream(Files.newOutputStream(tmp))) {
+        ByteStreams.copy(in, out);
       }
-      if (!tmp.renameTo(dst)) {
-        throw new IOException("Cannot rename " + tmp + " to " + dst);
+      tmp.toFile().setReadOnly();
+      try {
+        Files.move(tmp, dst);
+      } catch (IOException e) {
+        throw new IOException("Cannot rename " + tmp + " to " + dst, e);
       }
-      src.delete();
+      Files.delete(src);
     } catch (IOException e) {
       log.error("Cannot compress " + src, e);
-      tmp.delete();
+      try {
+        Files.deleteIfExists(tmp);
+      } catch (IOException e2) {
+        log.warn("Failed to delete temporary log file " + tmp, e2);
+      }
     }
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
index c713b79..048c2ee 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
@@ -24,14 +24,14 @@
 
 import org.eclipse.jgit.lib.Config;
 
-import java.io.File;
+import java.nio.file.Path;
 
 import javax.sql.DataSource;
 
 /** Loads the site library if not yet loaded. */
 @Singleton
 public class SiteLibraryBasedDataSourceProvider extends DataSourceProvider {
-  private final File libdir;
+  private final Path libdir;
   private boolean init;
 
   @Inject
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 02a8eac..010ea75 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -54,9 +54,11 @@
 import org.eclipse.jgit.util.FS;
 import org.kohsuke.args4j.Option;
 
-import java.io.File;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.ArrayList;
@@ -66,30 +68,30 @@
 
 public abstract class SiteProgram extends AbstractProgram {
   @Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data")
-  private File sitePath = new File(".");
+  private void setSitePath(String path) {
+    sitePath = Paths.get(path);
+  }
 
   protected Provider<DataSource> dsProvider;
 
+  private Path sitePath;
+
   protected SiteProgram() {
   }
 
-  protected SiteProgram(File sitePath, final Provider<DataSource> dsProvider) {
+  protected SiteProgram(Path sitePath, final Provider<DataSource> dsProvider) {
     this.sitePath = sitePath;
     this.dsProvider = dsProvider;
   }
 
   /** @return the site path specified on the command line. */
-  protected File getSitePath() {
-    File path = sitePath.getAbsoluteFile();
-    if (".".equals(path.getName())) {
-      path = path.getParentFile();
-    }
-    return path;
+  protected Path getSitePath() {
+    return sitePath;
   }
 
   /** Ensures we are running inside of a valid site, otherwise throws a Die. */
   protected void mustHaveValidSite() throws Die {
-    if (!new File(new File(getSitePath(), "etc"), "gerrit.config").exists()) {
+    if (!Files.exists(sitePath.resolve("etc").resolve("gerrit.config"))) {
       throw die("not a Gerrit site: '" + getSitePath() + "'\n"
           + "Perhaps you need to run init first?");
     }
@@ -97,13 +99,13 @@
 
   /** @return provides database connectivity and site path. */
   protected Injector createDbInjector(final DataSourceProvider.Context context) {
-    final File sitePath = getSitePath();
+    final Path sitePath = getSitePath();
     final List<Module> modules = new ArrayList<>();
 
     Module sitePathModule = new AbstractModule() {
       @Override
       protected void configure() {
-        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
         bind(String.class).annotatedWith(SecureStoreClassName.class)
             .toProvider(Providers.of(getConfiguredSecureStoreClass()));
       }
@@ -191,13 +193,14 @@
     Module m = new AbstractModule() {
       @Override
       protected void configure() {
-        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
         bind(SitePaths.class);
       }
     };
     Injector i = Guice.createInjector(m);
     SitePaths site = i.getInstance(SitePaths.class);
-    FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+    FileBasedConfig cfg =
+        new FileBasedConfig(site.gerrit_config.toFile(), FS.DETECTED);
     if (!cfg.getFile().exists()) {
       return null;
     }
@@ -222,7 +225,7 @@
     modules.add(new AbstractModule() {
       @Override
       protected void configure() {
-        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
       }
     });
     modules.add(new GerritServerConfigModule());
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
index 4d7370b..150309e 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/InitTestCase.java
@@ -16,11 +16,11 @@
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 
 public abstract class InitTestCase extends LocalDiskRepositoryTestCase {
-  protected File newSitePath() throws IOException {
-    return new File(createWorkRepository().getWorkTree(), "test_site");
+  protected Path newSitePath() throws IOException {
+    return createWorkRepository().getWorkTree().toPath().resolve("test_site");
   }
 }
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index a37c97d..7b08902 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -25,13 +25,13 @@
 
 import org.junit.Test;
 
-import java.io.File;
 import java.io.FileNotFoundException;
+import java.nio.file.Paths;
 
 public class LibrariesTest {
   @Test
   public void testCreate() throws FileNotFoundException {
-    final SitePaths site = new SitePaths(new File("."));
+    final SitePaths site = new SitePaths(Paths.get("."));
     final ConsoleUI ui = createStrictMock(ConsoleUI.class);
 
     replay(ui);
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
index 720d108..bb2fb94 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.init;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.easymock.EasyMock.createStrictMock;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
@@ -24,6 +25,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.Section;
@@ -34,13 +36,12 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.IO;
 import org.junit.Test;
 
-import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
-import java.io.Writer;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 
@@ -49,23 +50,18 @@
 
   @Test
   public void testUpgrade() throws IOException, ConfigInvalidException {
-    final File p = newSitePath();
+    final Path p = newSitePath();
     final SitePaths site = new SitePaths(p);
     assertTrue(site.isNew);
     assertTrue(site.site_path.mkdir());
-    assertTrue(site.etc_dir.mkdir());
+    Files.createDirectory(site.etc_dir);
 
     for (String n : UpgradeFrom2_0_x.etcFiles) {
-      Writer w = new FileWriter(new File(p, n));
-      try {
-        w.write("# " + n + "\n");
-      } finally {
-        w.close();
-      }
+      Files.write(p.resolve(n), ("# " + n + "\n").getBytes(UTF_8));
     }
 
     FileBasedConfig old =
-        new FileBasedConfig(new File(p, "gerrit.config"), FS.DETECTED);
+        new FileBasedConfig(p.resolve("gerrit.config").toFile(), FS.DETECTED);
 
     old.setString("ldap", null, "username", "ldap.user");
     old.setString("ldap", null, "password", "ldap.s3kr3t");
@@ -85,8 +81,11 @@
       }
     };
 
-    expect(ui.yesno(eq(true), eq("Upgrade '%s'"), eq(p.getCanonicalPath())))
-        .andReturn(true);
+    expect(ui.yesno(
+        eq(true),
+        eq("Upgrade '%s'"),
+        eq(p.toRealPath().normalize().toString())))
+      .andReturn(true);
     replay(ui);
 
     UpgradeFrom2_0_x u = new UpgradeFrom2_0_x(site, flags, ui, sections);
@@ -98,11 +97,14 @@
     for (String n : UpgradeFrom2_0_x.etcFiles) {
       if ("gerrit.config".equals(n)) continue;
       if ("secure.config".equals(n)) continue;
-      assertEquals("# " + n + "\n",//
-          new String(IO.readFully(new File(site.etc_dir, n)), "UTF-8"));
+      try (InputStream in = Files.newInputStream(site.etc_dir.resolve(n))) {
+        assertEquals("# " + n + "\n",
+            new String(ByteStreams.toByteArray(in), UTF_8));
+      }
     }
 
-    FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+    FileBasedConfig cfg =
+        new FileBasedConfig(site.gerrit_config.toFile(), FS.DETECTED);
     cfg.load();
 
     assertEquals("email.user", cfg.getString("sendemail", null, "smtpUser"));
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 40767e0..eb2ec31 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index b117b29..1da5d51 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-archetype</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <name>Gerrit Code Review - Plugin Archetype</name>
   <description>Maven Archetype for Gerrit Plugins</description>
   <url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index a7f2bbf..bd4d738 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwt-archetype</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <name>Gerrit Code Review - Web UI GWT Plugin Archetype</name>
   <description>Maven Archetype for Gerrit Web UI GWT Plugins</description>
   <url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index fd6fc9b..fa261be 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwtui</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin GWT UI</name>
   <description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index 796df19..11d4d83 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-js-archetype</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
   <description>Maven Archetype for Gerrit Web UI JavaScript Plugins</description>
   <url>http://code.google.com/p/gerrit/</url>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
index 07936d9..d181c35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java
@@ -39,7 +39,7 @@
 import java.util.List;
 import java.util.Map;
 
-class SuggestAccounts implements RestReadView<TopLevelResource> {
+public class SuggestAccounts implements RestReadView<TopLevelResource> {
   private static final int MAX_RESULTS = 100;
   private static final String MAX_SUFFIX = "\u9fa5";
 
@@ -50,9 +50,10 @@
   private final int suggestFrom;
 
   private int limit = 10;
+  private String query;
 
   @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of users to return")
-  void setLimit(int n) {
+  public void setLimit(int n) {
     if (n < 0) {
       limit = 10;
     } else if (n == 0) {
@@ -63,7 +64,9 @@
   }
 
   @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "match users")
-  private String query;
+  public void setQuery(String query) {
+    this.query = query;
+  }
 
   @Inject
   SuggestAccounts(AccountControl.Factory accountControlFactory,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index 0c02c99..cf041fd4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.api.accounts.AccountApi;
 import com.google.gerrit.extensions.api.accounts.Accounts;
+import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -24,24 +25,30 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.AccountsCollection;
+import com.google.gerrit.server.account.SuggestAccounts;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.util.List;
+
 @Singleton
 public class AccountsImpl extends Accounts.NotImplemented implements Accounts {
   private final AccountsCollection accounts;
   private final AccountApiImpl.Factory api;
   private final Provider<CurrentUser> self;
+  private final Provider<SuggestAccounts> suggestAccountsProvider;
 
   @Inject
   AccountsImpl(AccountsCollection accounts,
       AccountApiImpl.Factory api,
-      Provider<CurrentUser> self) {
+      Provider<CurrentUser> self,
+      Provider<SuggestAccounts> suggestAccountsProvider) {
     this.accounts = accounts;
     this.api = api;
     this.self = self;
+    this.suggestAccountsProvider = suggestAccountsProvider;
   }
 
   @Override
@@ -61,4 +68,32 @@
     }
     return api.create(new AccountResource((IdentifiedUser)self.get()));
   }
+
+  @Override
+  public SuggestAccountsRequest suggestAccounts() throws RestApiException {
+    return new SuggestAccountsRequest() {
+      @Override
+      public List<AccountInfo> get() throws RestApiException {
+        return AccountsImpl.this.suggestAccounts(this);
+      }
+    };
+  }
+
+  @Override
+  public SuggestAccountsRequest suggestAccounts(String query)
+    throws RestApiException {
+    return suggestAccounts().withQuery(query);
+  }
+
+  private List<AccountInfo> suggestAccounts(SuggestAccountsRequest r)
+    throws RestApiException {
+    try {
+      SuggestAccounts mySuggestAccounts = suggestAccountsProvider.get();
+      mySuggestAccounts.setQuery(r.getQuery());
+      mySuggestAccounts.setLimit(r.getLimit());
+      return mySuggestAccounts.apply(TopLevelResource.INSTANCE);
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot retrieve suggested accounts", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 16472e3..1555cdd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -57,17 +57,15 @@
     return getControl().getNotes();
   }
 
-  @Override
-  public String getETag() {
-    CurrentUser user = control.getCurrentUser();
-    Hasher h = Hashing.md5().newHasher()
-      .putLong(getChange().getLastUpdatedOn().getTime())
+
+  // This includes all information relevant for ETag computation
+  // unrelated to the UI.
+  public void prepareETag(Hasher h, CurrentUser user) {
+    h.putLong(getChange().getLastUpdatedOn().getTime())
       .putInt(getChange().getRowVersion())
-      .putBoolean(user.getStarredChanges().contains(getChange().getId()))
       .putInt(user.isIdentifiedUser()
           ? ((IdentifiedUser) user).getAccountId().get()
           : 0);
-
     byte[] buf = new byte[20];
     ObjectId noteId;
     try {
@@ -82,6 +80,14 @@
     for (ProjectState p : control.getProjectControl().getProjectState().tree()) {
       hashObjectId(h, p.getConfig().getRevision(), buf);
     }
+  }
+
+  @Override
+  public String getETag() {
+    CurrentUser user = control.getCurrentUser();
+    Hasher h = Hashing.md5().newHasher()
+        .putBoolean(user.getStarredChanges().contains(getChange().getId()));
+    prepareETag(h, user);
     return h.hash().toString();
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index d58c8d2..b21bee2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -14,22 +14,59 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.common.base.Strings;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.gerrit.extensions.restapi.ETagView;
 import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
-@Singleton
-public class GetRevisionActions implements RestReadView<RevisionResource> {
-  private final ActionJson delegate;
+import org.eclipse.jgit.lib.Config;
 
+@Singleton
+public class GetRevisionActions implements ETagView<RevisionResource> {
+  private final ActionJson delegate;
+  private final Provider<InternalChangeQuery> queryProvider;
+  private final Config config;
   @Inject
-  GetRevisionActions(ActionJson delegate) {
+  GetRevisionActions(
+      ActionJson delegate,
+      Provider<InternalChangeQuery> queryProvider,
+      @GerritServerConfig Config config) {
     this.delegate = delegate;
+    this.queryProvider = queryProvider;
+    this.config = config;
   }
 
   @Override
   public Object apply(RevisionResource rsrc) {
     return Response.withMustRevalidate(delegate.format(rsrc));
   }
+
+  @Override
+  public String getETag(RevisionResource rsrc) {
+    String topic = rsrc.getChange().getTopic();
+    if (!Submit.wholeTopicEnabled(config)
+        || Strings.isNullOrEmpty(topic)) {
+      return rsrc.getETag();
+    }
+    Hasher h = Hashing.md5().newHasher();
+    CurrentUser user = rsrc.getControl().getCurrentUser();
+    try {
+      for (ChangeData c : queryProvider.get().byTopicOpen(topic)) {
+        new ChangeResource(c.changeControl()).prepareETag(h, user);
+      }
+    } catch (OrmException e){
+      throw new OrmRuntimeException(e);
+    }
+    return h.hash().toString();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 41151ae..03bc7f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -169,7 +169,7 @@
     this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
         cfg.getString("change", null, "submitTooltip"),
         DEFAULT_TOOLTIP));
-    submitWholeTopic = cfg.getBoolean("change", null, "submitWholeTopic" , false);
+    submitWholeTopic = wholeTopicEnabled(cfg);
     this.submitTopicLabel = MoreObjects.firstNonNull(
         Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
         "Submit whole topic");
@@ -206,17 +206,19 @@
           rsrc.getPatchSet().getRevision().get()));
     }
 
-    change = submit(rsrc, caller, false);
-    if (change == null) {
-      throw new ResourceConflictException("change is "
-          + status(dbProvider.get().changes().get(rsrc.getChange().getId())));
-    }
+    List<Change> submittedChanges = submit(rsrc, caller, false);
 
     if (input.waitForMerge) {
-      mergeQueue.merge(change.getDest());
+      for (Change c : submittedChanges) {
+        // TODO(sbeller): We should make schedule return a Future, then we
+        // could do these all in parallel and still block until they're done.
+        mergeQueue.merge(c.getDest());
+      }
       change = dbProvider.get().changes().get(change.getId());
     } else {
-      mergeQueue.schedule(change.getDest());
+      for (Change c : submittedChanges) {
+        mergeQueue.schedule(c.getDest());
+      }
     }
 
     if (change == null) {
@@ -345,9 +347,10 @@
         .orNull();
   }
 
-  private Change submitToDatabase(ReviewDb db, Change.Id changeId,
-      final Timestamp timestamp) throws OrmException {
-    return db.changes().atomicUpdate(changeId,
+  private Change submitToDatabase(final ReviewDb db, final Change.Id changeId,
+      final Timestamp timestamp) throws OrmException,
+      ResourceConflictException {
+    Change ret = db.changes().atomicUpdate(changeId,
       new AtomicUpdate<Change>() {
         @Override
         public Change update(Change change) {
@@ -359,6 +362,12 @@
           return null;
         }
       });
+    if (ret != null) {
+      return ret;
+    } else {
+      throw new ResourceConflictException("change " + changeId + " is "
+          + status(db.changes().get(changeId)));
+    }
   }
 
   private Change submitThisChange(RevisionResource rsrc, IdentifiedUser caller,
@@ -380,9 +389,6 @@
       // Write update commit after all normalized label commits.
       batch.write(update, new CommitBuilder());
       change = submitToDatabase(db, change.getId(), timestamp);
-      if (change == null) {
-        return null;
-      }
       db.commit();
     } finally {
       db.rollback();
@@ -391,7 +397,7 @@
     return change;
   }
 
-  private Change submitWholeTopic(RevisionResource rsrc, IdentifiedUser caller,
+  private List<Change> submitWholeTopic(RevisionResource rsrc, IdentifiedUser caller,
       boolean force, String topic) throws ResourceConflictException, OrmException,
       IOException {
     Preconditions.checkNotNull(topic);
@@ -420,30 +426,31 @@
       batch.write(update, new CommitBuilder());
 
       for (ChangeData c : changesByTopic) {
-        if (submitToDatabase(db, c.getId(), timestamp) == null) {
-          return null;
-        }
+        submitToDatabase(db, c.getId(), timestamp);
       }
       db.commit();
     } finally {
       db.rollback();
     }
     List<Change.Id> ids = new ArrayList<>(changesByTopic.size());
+    List<Change> ret = new ArrayList<>(changesByTopic.size());
     for (ChangeData c : changesByTopic) {
       ids.add(c.getId());
+      ret.add(c.change());
     }
     indexer.indexAsync(ids).checkedGet();
-    return change;
+
+    return ret;
   }
 
-  public Change submit(RevisionResource rsrc, IdentifiedUser caller,
+  public List<Change> submit(RevisionResource rsrc, IdentifiedUser caller,
       boolean force) throws ResourceConflictException, OrmException,
       IOException {
     String topic = rsrc.getChange().getTopic();
     if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
       return submitWholeTopic(rsrc, caller, force, topic);
     } else {
-      return submitThisChange(rsrc, caller, force);
+      return Arrays.asList(submitThisChange(rsrc, caller, force));
     }
   }
 
@@ -654,6 +661,10 @@
     return new RevisionResource(changes.parse(target), rsrc.getPatchSet());
   }
 
+  static boolean wholeTopicEnabled(Config config) {
+    return config.getBoolean("change", null, "submitWholeTopic" , false);
+  }
+
   public static class CurrentRevision implements
       RestModifyView<ChangeResource, SubmitInput> {
     private final Provider<ReviewDb> dbProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
index aa699c5..4b1236b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java
@@ -44,10 +44,11 @@
 
   @Override
   public Config get() {
-    FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config, FS.DETECTED);
+    FileBasedConfig cfg =
+        new FileBasedConfig(site.gerrit_config.toFile(), FS.DETECTED);
 
     if (!cfg.getFile().exists()) {
-      log.info("No " + site.gerrit_config.getAbsolutePath()
+      log.info("No " + site.gerrit_config.toAbsolutePath()
           + "; assuming defaults");
       return new GerritConfig(cfg, secureStore);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetSummary.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetSummary.java
index 9aa8590..feac473 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetSummary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetSummary.java
@@ -24,7 +24,6 @@
 import org.eclipse.jgit.internal.storage.file.WindowCacheStatAccessor;
 import org.kohsuke.args4j.Option;
 
-import java.io.File;
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.lang.management.OperatingSystemMXBean;
@@ -33,6 +32,8 @@
 import java.lang.management.ThreadMXBean;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -43,7 +44,7 @@
 public class GetSummary implements RestReadView<ConfigResource> {
 
   private final WorkQueue workQueue;
-  private final File sitePath;
+  private final Path sitePath;
 
   @Option(name = "--gc", usage = "perform Java GC before retrieving memory stats")
   private boolean gc;
@@ -62,7 +63,7 @@
   }
 
   @Inject
-  public GetSummary(WorkQueue workQueue, @SitePath File sitePath) {
+  public GetSummary(WorkQueue workQueue, @SitePath Path sitePath) {
     this.workQueue = workQueue;
     this.sitePath = sitePath;
   }
@@ -186,7 +187,8 @@
     } catch (UnknownHostException e) {
     }
 
-    jvmSummary.currentWorkingDirectory = path(new File(".").getAbsoluteFile().getParentFile());
+    jvmSummary.currentWorkingDirectory =
+        path(Paths.get(".").toAbsolutePath().getParent());
     jvmSummary.site = path(sitePath);
     return jvmSummary;
   }
@@ -210,11 +212,11 @@
     return String.format("%1$6.2f%2$s", value, suffix).trim();
   }
 
-  private static String path(File file) {
+  private static String path(Path path) {
     try {
-      return file.getCanonicalPath();
+      return path.toRealPath().normalize().toString();
     } catch (IOException err) {
-      return file.getAbsolutePath();
+      return path.toAbsolutePath().normalize().toString();
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 76f5323..3754674 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -35,6 +35,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Map;
 
 @Singleton
@@ -61,7 +62,7 @@
     this.projectStateFactory = projectStateFactory;
     this.pluginConfigs = Maps.newHashMap();
 
-    this.cfgSnapshot = FileSnapshot.save(site.gerrit_config);
+    this.cfgSnapshot = FileSnapshot.save(site.gerrit_config.toFile());
     this.cfg = cfgProvider.get();
   }
 
@@ -103,8 +104,9 @@
    * @return the plugin configuration from the 'gerrit.config' file
    */
   public PluginConfig getFromGerritConfig(String pluginName, boolean refresh) {
-    if (refresh && cfgSnapshot.isModified(site.gerrit_config)) {
-      cfgSnapshot = FileSnapshot.save(site.gerrit_config);
+    File configFile = site.gerrit_config.toFile();
+    if (refresh && cfgSnapshot.isModified(configFile)) {
+      cfgSnapshot = FileSnapshot.save(configFile);
       cfg = cfgProvider.get();
     }
     return new PluginConfig(pluginName, cfg);
@@ -250,20 +252,21 @@
       return pluginConfigs.get(pluginName);
     }
 
-    File pluginConfigFile = new File(site.etc_dir, pluginName + ".config");
-    FileBasedConfig cfg = new FileBasedConfig(pluginConfigFile, FS.DETECTED);
+    Path pluginConfigFile = site.etc_dir.resolve(pluginName + ".config");
+    FileBasedConfig cfg =
+        new FileBasedConfig(pluginConfigFile.toFile(), FS.DETECTED);
     pluginConfigs.put(pluginName, cfg);
     if (!cfg.getFile().exists()) {
-      log.info("No " + pluginConfigFile.getAbsolutePath() + "; assuming defaults");
+      log.info("No " + pluginConfigFile.toAbsolutePath() + "; assuming defaults");
       return cfg;
     }
 
     try {
       cfg.load();
     } catch (IOException e) {
-      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+      log.warn("Failed to load " + pluginConfigFile.toAbsolutePath(), e);
     } catch (ConfigInvalidException e) {
-      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+      log.warn("Failed to load " + pluginConfigFile.toAbsolutePath(), e);
     }
 
     return cfg;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index fbff7c4..1b8f373 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Path;
 
 /** Important paths within a {@link SitePath}. */
 @Singleton
@@ -29,74 +30,76 @@
   public static final String FOOTER_FILENAME = "GerritSiteFooter.html";
 
   public final File site_path;
-  public final File bin_dir;
-  public final File etc_dir;
-  public final File lib_dir;
-  public final File tmp_dir;
-  public final File logs_dir;
-  public final File plugins_dir;
-  public final File data_dir;
+  public final Path bin_dir;
+  public final Path etc_dir;
+  public final Path lib_dir;
+  public final Path tmp_dir;
+  public final Path logs_dir;
+  public final Path plugins_dir;
+  public final Path data_dir;
   public final File mail_dir;
   public final File hooks_dir;
   public final File static_dir;
   public final File themes_dir;
   public final File index_dir;
 
-  public final File gerrit_sh;
-  public final File gerrit_war;
+  public final Path gerrit_sh;
+  public final Path gerrit_war;
 
-  public final File gerrit_config;
-  public final File secure_config;
-  public final File contact_information_pub;
+  public final Path gerrit_config;
+  public final Path secure_config;
+  public final Path contact_information_pub;
 
-  public final File ssl_keystore;
-  public final File ssh_key;
-  public final File ssh_rsa;
-  public final File ssh_dsa;
-  public final File peer_keys;
+  public final Path ssl_keystore;
+  public final Path ssh_key;
+  public final Path ssh_rsa;
+  public final Path ssh_dsa;
+  public final Path peer_keys;
 
-  public final File site_css;
-  public final File site_header;
-  public final File site_footer;
-  public final File site_gitweb;
+  public final Path site_css;
+  public final Path site_header;
+  public final Path site_footer;
+  public final Path site_gitweb;
 
   /** {@code true} if {@link #site_path} has not been initialized. */
   public final boolean isNew;
 
   @Inject
-  public SitePaths(final @SitePath File sitePath) throws FileNotFoundException {
-    site_path = sitePath;
+  public SitePaths(final @SitePath Path sitePath) throws FileNotFoundException {
+    // TODO(dborowitz): Convert all of these to Paths.
+    site_path = sitePath.toFile();
+    Path p = sitePath;
 
-    bin_dir = new File(site_path, "bin");
-    etc_dir = new File(site_path, "etc");
-    lib_dir = new File(site_path, "lib");
-    tmp_dir = new File(site_path, "tmp");
-    plugins_dir = new File(site_path, "plugins");
-    data_dir = new File(site_path, "data");
-    logs_dir = new File(site_path, "logs");
-    mail_dir = new File(etc_dir, "mail");
+    bin_dir = p.resolve("bin");
+    etc_dir = p.resolve("etc");
+    lib_dir = p.resolve("lib");
+    tmp_dir = p.resolve("tmp");
+    plugins_dir = p.resolve("plugins");
+    data_dir = p.resolve("data");
+    logs_dir = p.resolve("logs");
+    mail_dir = etc_dir.resolve("mail").toFile();
     hooks_dir = new File(site_path, "hooks");
     static_dir = new File(site_path, "static");
     themes_dir = new File(site_path, "themes");
     index_dir = new File(site_path, "index");
 
-    gerrit_sh = new File(bin_dir, "gerrit.sh");
-    gerrit_war = new File(bin_dir, "gerrit.war");
+    gerrit_sh = bin_dir.resolve("gerrit.sh");
+    gerrit_war = bin_dir.resolve("gerrit.war");
 
-    gerrit_config = new File(etc_dir, "gerrit.config");
-    secure_config = new File(etc_dir, "secure.config");
-    contact_information_pub = new File(etc_dir, "contact_information.pub");
+    gerrit_config = etc_dir.resolve("gerrit.config");
+    secure_config = etc_dir.resolve("secure.config");
+    contact_information_pub = etc_dir.resolve("contact_information.pub");
 
-    ssl_keystore = new File(etc_dir, "keystore");
-    ssh_key = new File(etc_dir, "ssh_host_key");
-    ssh_rsa = new File(etc_dir, "ssh_host_rsa_key");
-    ssh_dsa = new File(etc_dir, "ssh_host_dsa_key");
-    peer_keys = new File(etc_dir, "peer_keys");
+    ssl_keystore = etc_dir.resolve("keystore");
+    ssh_key = etc_dir.resolve("ssh_host_key");
+    ssh_rsa = etc_dir.resolve("ssh_host_rsa_key");
+    ssh_dsa = etc_dir.resolve("ssh_host_dsa_key");
+    peer_keys = etc_dir.resolve("peer_keys");
 
-    site_css = new File(etc_dir, CSS_FILENAME);
-    site_header = new File(etc_dir, HEADER_FILENAME);
-    site_footer = new File(etc_dir, FOOTER_FILENAME);
-    site_gitweb = new File(etc_dir, "gitweb_config.perl");
+    site_css = etc_dir.resolve(CSS_FILENAME);
+    site_header = etc_dir.resolve(HEADER_FILENAME);
+    site_footer = etc_dir.resolve(FOOTER_FILENAME);
+    site_gitweb = etc_dir.resolve("gitweb_config.perl");
 
     if (site_path.exists()) {
       final String[] contents = site_path.list();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java
index 6b195de..f6e08b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreModule.java
@@ -28,11 +28,12 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.util.StringUtils;
 
-import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.Security;
 
 /** Creates the {@link ContactStore} based on the configuration. */
@@ -46,7 +47,7 @@
   public ContactStore provideContactStore(@GerritServerConfig final Config config,
       final SitePaths site, final SchemaFactory<ReviewDb> schema,
       final ContactStoreConnection.Factory connFactory) {
-    final String url = config.getString("contactstore", null, "url");
+    String url = config.getString("contactstore", null, "url");
     if (StringUtils.isEmptyOrNull(url)) {
       return new NoContactStore();
     }
@@ -56,18 +57,18 @@
           + " needed to encrypt contact information");
     }
 
-    final URL storeUrl;
+    URL storeUrl;
     try {
       storeUrl = new URL(url);
     } catch (MalformedURLException e) {
       throw new ProvisionException("Invalid contactstore.url: " + url, e);
     }
 
-    final String storeAPPSEC = config.getString("contactstore", null, "appsec");
-    final File pubkey = site.contact_information_pub;
-    if (!pubkey.exists()) {
+    String storeAPPSEC = config.getString("contactstore", null, "appsec");
+    Path pubkey = site.contact_information_pub;
+    if (!Files.exists(pubkey)) {
       throw new ProvisionException("PGP public key file \""
-          + pubkey.getAbsolutePath() + "\" not found");
+          + pubkey.toAbsolutePath() + "\" not found");
     }
     return new EncryptedContactStore(storeUrl, storeAPPSEC, pubkey, schema,
         connFactory);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
index d8dbfed..e27f63d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
@@ -42,12 +42,12 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.SecureRandom;
@@ -72,7 +72,7 @@
   private final ContactStoreConnection.Factory connFactory;
 
   EncryptedContactStore(final URL storeUrl, final String storeAPPSEC,
-      final File pubKey, final SchemaFactory<ReviewDb> schema,
+      final Path pubKey, final SchemaFactory<ReviewDb> schema,
       final ContactStoreConnection.Factory connFactory) {
     this.storeUrl = storeUrl;
     this.storeAPPSEC = storeAPPSEC;
@@ -104,8 +104,8 @@
     return true;
   }
 
-  private static PGPPublicKeyRingCollection readPubRing(final File pub) {
-    try (InputStream fin = new FileInputStream(pub);
+  private static PGPPublicKeyRingCollection readPubRing(Path pub) {
+    try (InputStream fin = Files.newInputStream(pub);
         InputStream in = PGPUtil.getDecoderStream(fin)) {
         return new PGPPublicKeyRingCollection(in);
     } catch (IOException | PGPException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 81833fe..be584d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1726,18 +1726,15 @@
       throws OrmException, IOException {
     Submit submit = submitProvider.get();
     RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
-    Change c;
+    List<Change> changes;
     try {
       // Force submit even if submit rule evaluation fails.
-      c = submit.submit(rsrc, currentUser, true);
+      changes = submit.submit(rsrc, currentUser, true);
     } catch (ResourceConflictException e) {
       throw new IOException(e);
     }
-    if (c == null) {
-      addError("Submitting change " + changeCtl.getChange().getChangeId()
-          + " failed.");
-    } else {
-      addMessage("");
+    addMessage("");
+    for (Change c : changes) {
       mergeQueue.merge(c.getDest());
       c = db.changes().get(c.getId());
       switch (c.getStatus()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
index 593f2c9..9827812 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
@@ -14,17 +14,17 @@
 
 package com.google.gerrit.server.plugins;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.jar.JarFile;
 
 class CleanupHandle {
-  private final File tmpFile;
+  private final Path tmp;
   private final JarFile jarFile;
 
-  CleanupHandle(File tmpFile,
-      JarFile jarFile) {
-    this.tmpFile = tmpFile;
+  CleanupHandle(Path tmp, JarFile jarFile) {
+    this.tmp = tmp;
     this.jarFile = jarFile;
   }
 
@@ -34,12 +34,13 @@
     } catch (IOException err) {
       PluginLoader.log.error("Cannot close " + jarFile.getName(), err);
     }
-    if (!tmpFile.delete() && tmpFile.exists()) {
-      PluginLoader.log.warn("Cannot delete " + tmpFile.getAbsolutePath()
-          + ", retrying to delete it on termination of the virtual machine");
-      tmpFile.deleteOnExit();
-    } else {
-      PluginLoader.log.info("Cleaned plugin " + tmpFile.getName());
+    try {
+      Files.deleteIfExists(tmp);
+      PluginLoader.log.info("Cleaned plugin " + tmp.getFileName());
+    } catch (IOException e) {
+      PluginLoader.log.warn("Cannot delete " + tmp.toAbsolutePath()
+          + ", retrying to delete it on termination of the virtual machine", e);
+      tmp.toFile().deleteOnExit();
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
index 7252617..1d4233a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CopyConfigModule.java
@@ -33,7 +33,7 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 
-import java.io.File;
+import java.nio.file.Path;
 
 /**
  * Copies critical objects from the {@code dbInjector} into a plugin.
@@ -47,11 +47,11 @@
 class CopyConfigModule extends AbstractModule {
   @Inject
   @SitePath
-  private File sitePath;
+  private Path sitePath;
 
   @Provides
   @SitePath
-  File getSitePath() {
+  Path getSitePath() {
     return sitePath;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index 53f39f1..dcfb52c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -24,13 +24,14 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -43,7 +44,7 @@
   static final String JAR_EXTENSION = ".jar";
   static final Logger log = LoggerFactory.getLogger(JarPluginProvider.class);
 
-  private final File tmpDir;
+  private final Path tmpDir;
 
   @Inject
   JarPluginProvider(SitePaths sitePaths) {
@@ -51,42 +52,42 @@
   }
 
   @Override
-  public boolean handles(File srcFile) {
-    String fileName = srcFile.getName();
+  public boolean handles(Path srcPath) {
+    String fileName = srcPath.getFileName().toString();
     return fileName.endsWith(JAR_EXTENSION)
         || fileName.endsWith(JAR_EXTENSION + ".disabled");
   }
 
   @Override
-  public String getPluginName(File srcFile) {
+  public String getPluginName(Path srcPath) {
     try {
-      return MoreObjects.firstNonNull(getJarPluginName(srcFile),
-          PluginLoader.nameOf(srcFile));
+      return MoreObjects.firstNonNull(getJarPluginName(srcPath),
+          PluginLoader.nameOf(srcPath));
     } catch (IOException e) {
-      throw new IllegalArgumentException("Invalid plugin file " + srcFile
+      throw new IllegalArgumentException("Invalid plugin file " + srcPath
           + ": cannot get plugin name", e);
     }
   }
 
-  public static String getJarPluginName(File srcFile) throws IOException {
-    try (JarFile jarFile = new JarFile(srcFile)) {
+  public static String getJarPluginName(Path srcPath) throws IOException {
+    try (JarFile jarFile = new JarFile(srcPath.toFile())) {
       return jarFile.getManifest().getMainAttributes()
           .getValue("Gerrit-PluginName");
     }
   }
 
   @Override
-  public ServerPlugin get(File srcFile, FileSnapshot snapshot,
+  public ServerPlugin get(Path srcPath, FileSnapshot snapshot,
       PluginDescription description) throws InvalidPluginException {
     try {
-      String name = getPluginName(srcFile);
-      String extension = getExtension(srcFile);
-      try (FileInputStream in = new FileInputStream(srcFile)) {
-        File tmp = asTemp(in, tempNameFor(name), extension, tmpDir);
-        return loadJarPlugin(name, srcFile, snapshot, tmp, description);
+      String name = getPluginName(srcPath);
+      String extension = getExtension(srcPath);
+      try (InputStream in = Files.newInputStream(srcPath)) {
+        Path tmp = asTemp(in, tempNameFor(name), extension, tmpDir);
+        return loadJarPlugin(name, srcPath, snapshot, tmp, description);
       }
     } catch (IOException e) {
-      throw new InvalidPluginException("Cannot load Jar plugin " + srcFile, e);
+      throw new InvalidPluginException("Cannot load Jar plugin " + srcPath, e);
     }
   }
 
@@ -95,8 +96,8 @@
     return "gerrit";
   }
 
-  private static String getExtension(File file) {
-    return getExtension(file.getName());
+  private static String getExtension(Path path) {
+    return getExtension(path.getFileName().toString());
   }
 
   private static String getExtension(String name) {
@@ -109,18 +110,18 @@
     return PLUGIN_TMP_PREFIX + name + "_" + fmt.format(new Date()) + "_";
   }
 
-  public static File storeInTemp(String pluginName, InputStream in,
+  public static Path storeInTemp(String pluginName, InputStream in,
       SitePaths sitePaths) throws IOException {
-    if (!sitePaths.tmp_dir.exists()) {
-      sitePaths.tmp_dir.mkdirs();
+    if (!Files.exists(sitePaths.tmp_dir)) {
+      Files.createDirectories(sitePaths.tmp_dir);
     }
     return asTemp(in, tempNameFor(pluginName), ".jar", sitePaths.tmp_dir);
   }
 
-  private ServerPlugin loadJarPlugin(String name, File srcJar,
-      FileSnapshot snapshot, File tmp, PluginDescription description)
+  private ServerPlugin loadJarPlugin(String name, Path srcJar,
+      FileSnapshot snapshot, Path tmp, PluginDescription description)
       throws IOException, InvalidPluginException, MalformedURLException {
-    JarFile jarFile = new JarFile(tmp);
+    JarFile jarFile = new JarFile(tmp.toFile());
     boolean keep = false;
     try {
       Manifest manifest = jarFile.getManifest();
@@ -129,24 +130,22 @@
       List<URL> urls = new ArrayList<>(2);
       String overlay = System.getProperty("gerrit.plugin-classes");
       if (overlay != null) {
-        File classes = new File(new File(new File(overlay), name), "main");
-        if (classes.isDirectory()) {
-          log.info(String.format("plugin %s: including %s", name,
-              classes.getPath()));
-          urls.add(classes.toURI().toURL());
+        Path classes = Paths.get(overlay).resolve(name).resolve("main");
+        if (Files.isDirectory(classes)) {
+          log.info(String.format("plugin %s: including %s", name, classes));
+          urls.add(classes.toUri().toURL());
         }
       }
-      urls.add(tmp.toURI().toURL());
+      urls.add(tmp.toUri().toURL());
 
       ClassLoader pluginLoader =
           new URLClassLoader(urls.toArray(new URL[urls.size()]),
               PluginLoader.parentFor(type));
 
       JarScanner jarScanner = createJarScanner(srcJar);
-      ServerPlugin plugin =
-          new ServerPlugin(name, description.canonicalUrl, description.user,
-              srcJar, snapshot, jarScanner, description.dataDir,
-              pluginLoader);
+      ServerPlugin plugin = new ServerPlugin(name, description.canonicalUrl,
+          description.user, srcJar, snapshot, jarScanner,
+          description.dataDir, pluginLoader);
       plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile));
       keep = true;
       return plugin;
@@ -157,7 +156,7 @@
     }
   }
 
-  private JarScanner createJarScanner(File srcJar)
+  private JarScanner createJarScanner(Path srcJar)
       throws InvalidPluginException {
     try {
       return new JarScanner(srcJar);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index a8600fe..0f4aa6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -39,10 +39,10 @@
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -69,8 +69,8 @@
 
   private final JarFile jarFile;
 
-  public JarScanner(File srcFile) throws IOException {
-    this.jarFile = new JarFile(srcFile);
+  public JarScanner(Path src) throws IOException {
+    this.jarFile = new JarFile(src.toFile());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
index 63f69b5..8da8cc1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -27,12 +27,12 @@
 
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
-import java.io.File;
+import java.nio.file.Path;
 
 class JsPlugin extends Plugin {
   private Injector httpInjector;
 
-  JsPlugin(String name, File srcFile, PluginUser pluginUser,
+  JsPlugin(String name, Path srcFile, PluginUser pluginUser,
       FileSnapshot snapshot) {
     super(name, srcFile, pluginUser, snapshot, ApiType.JS);
   }
@@ -40,7 +40,7 @@
   @Override
   @Nullable
   public String getVersion() {
-    String fileName = getSrcFile().getName();
+    String fileName = getSrcFile().getFileName().toString();
     int firstDash = fileName.indexOf("-");
     if (firstDash > 0) {
       return fileName.substring(firstDash + 1, fileName.lastIndexOf(".js"));
@@ -51,7 +51,7 @@
   @Override
   public void start(PluginGuiceEnvironment env) throws Exception {
     manager = new LifecycleManager();
-    String fileName = getSrcFile().getName();
+    String fileName = getSrcFile().getFileName().toString();
     httpInjector =
         Guice.createInjector(new StandaloneJsPluginModule(getName(), fileName));
     manager.start();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 54f05f0..1d717ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -90,7 +90,7 @@
         stdout.format("%-30s %-10s %-8s %s\n", p.getName(),
             Strings.nullToEmpty(info.version),
             p.isDisabled() ? "DISABLED" : "ENABLED",
-            p.getSrcFile().getName());
+            p.getSrcFile().getFileName());
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
index 82a6ad9..cf38310 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
@@ -18,14 +18,14 @@
 import com.google.common.base.Joiner;
 import com.google.common.collect.Iterables;
 
-import java.io.File;
+import java.nio.file.Path;
 
 class MultipleProvidersForPluginException extends IllegalArgumentException {
   private static final long serialVersionUID = 1L;
 
-  MultipleProvidersForPluginException(File pluginSrcFile,
+  MultipleProvidersForPluginException(Path pluginSrcPath,
       Iterable<ServerPluginProvider> providersHandlers) {
-    super(pluginSrcFile.getAbsolutePath()
+    super(pluginSrcPath.toAbsolutePath()
         + " is claimed to be handled by more than one plugin provider: "
         + providersListToString(providersHandlers));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index b227909..6b84c21 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.plugins;
 
+import static com.google.gerrit.common.FileUtil.lastModified;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
@@ -25,7 +27,7 @@
 
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
-import java.io.File;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import java.util.jar.Attributes;
@@ -67,7 +69,7 @@
   }
 
   private final String name;
-  private final File srcFile;
+  private final Path srcFile;
   private final ApiType apiType;
   private final boolean disabled;
   private final CacheKey cacheKey;
@@ -80,17 +82,17 @@
   private List<ReloadableRegistrationHandle<?>> reloadableHandles;
 
   public Plugin(String name,
-      File srcFile,
+      Path srcPath,
       PluginUser pluginUser,
       FileSnapshot snapshot,
       ApiType apiType) {
     this.name = name;
-    this.srcFile = srcFile;
+    this.srcFile = srcPath;
     this.apiType = apiType;
     this.snapshot = snapshot;
     this.pluginUser = pluginUser;
     this.cacheKey = new Plugin.CacheKey(name);
-    this.disabled = srcFile.getName().endsWith(".disabled");
+    this.disabled = srcPath.getFileName().toString().endsWith(".disabled");
   }
 
   public CleanupHandle getCleanupHandle() {
@@ -105,7 +107,7 @@
     return pluginUser;
   }
 
-  public File getSrcFile() {
+  public Path getSrcFile() {
     return srcFile;
   }
 
@@ -168,7 +170,7 @@
 
   abstract boolean canReload();
 
-  boolean isModified(File jar) {
-    return snapshot.lastModified() != jar.lastModified();
+  boolean isModified(Path jar) {
+    return snapshot.lastModified() != lastModified(jar);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java
index 0228509..1d9cd0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java
@@ -11,14 +11,15 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package com.google.gerrit.server.plugins;
 
 import com.google.common.base.Optional;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
+import java.nio.file.NoSuchFileException;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Map;
@@ -57,7 +58,7 @@
 
     @Override
     public InputStream getInputStream(PluginEntry entry) throws IOException {
-      throw new FileNotFoundException("Empty plugin");
+      throw new NoSuchFileException("Empty plugin");
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index b51359d..17bb634 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -18,6 +18,8 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicate;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Lists;
@@ -25,6 +27,7 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
@@ -45,14 +48,15 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.AbstractMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -71,13 +75,13 @@
   static final String PLUGIN_TMP_PREFIX = "plugin_";
   static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
 
-  public String getPluginName(File srcFile) {
-    return MoreObjects.firstNonNull(getGerritPluginName(srcFile),
-        nameOf(srcFile));
+  public String getPluginName(Path srcPath) {
+    return MoreObjects.firstNonNull(getGerritPluginName(srcPath),
+        nameOf(srcPath));
   }
 
-  private final File pluginsDir;
-  private final File dataDir;
+  private final Path pluginsDir;
+  private final Path dataDir;
   private final PluginGuiceEnvironment env;
   private final ServerInformationImpl srvInfoImpl;
   private final PluginUser.Factory pluginUserFactory;
@@ -158,7 +162,7 @@
     checkRemoteInstall();
 
     String fileName = originalName;
-    File tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir);
+    Path tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir);
     String name = MoreObjects.firstNonNull(getGerritPluginName(tmp),
         nameOf(fileName));
     if (!originalName.equals(name)) {
@@ -168,26 +172,26 @@
     }
 
     String fileExtension = getExtension(fileName);
-    File dst = new File(pluginsDir, name + fileExtension);
+    Path dst = pluginsDir.resolve(name + fileExtension);
     synchronized (this) {
       Plugin active = running.get(name);
       if (active != null) {
-        fileName = active.getSrcFile().getName();
+        fileName = active.getSrcFile().getFileName().toString();
         log.info(String.format("Replacing plugin %s", active.getName()));
-        File old = new File(pluginsDir, ".last_" + fileName);
-        old.delete();
-        active.getSrcFile().renameTo(old);
+        Path old = pluginsDir.resolve(".last_" + fileName);
+        Files.deleteIfExists(old);
+        Files.move(active.getSrcFile(), old);
       }
 
-      new File(pluginsDir, fileName + ".disabled").delete();
-      tmp.renameTo(dst);
+      Files.deleteIfExists(pluginsDir.resolve(fileName + ".disabled"));
+      Files.move(tmp, dst);
       try {
         Plugin plugin = runPlugin(name, dst, active);
         if (active == null) {
           log.info(String.format("Installed plugin %s", plugin.getName()));
         }
       } catch (PluginInstallException e) {
-        dst.delete();
+        Files.deleteIfExists(dst);
         throw e;
       }
 
@@ -195,21 +199,17 @@
     }
   }
 
-  static File asTemp(InputStream in, String prefix, String suffix, File dir)
+  static Path asTemp(InputStream in, String prefix, String suffix, Path dir)
       throws IOException {
-    File tmp = File.createTempFile(prefix, suffix, dir);
+    Path tmp = Files.createTempFile(dir, prefix, suffix);
     boolean keep = false;
-    try (FileOutputStream out = new FileOutputStream(tmp)) {
-      byte[] data = new byte[8192];
-      int n;
-      while ((n = in.read(data)) > 0) {
-        out.write(data, 0, n);
-      }
+    try (OutputStream out = Files.newOutputStream(tmp)) {
+      ByteStreams.copy(in, out);
       keep = true;
       return tmp;
     } finally {
       if (!keep) {
-        tmp.delete();
+        Files.delete(tmp);
       }
     }
   }
@@ -240,12 +240,21 @@
         }
 
         log.info(String.format("Disabling plugin %s", active.getName()));
-        File off = new File(active.getSrcFile() + ".disabled");
-        active.getSrcFile().renameTo(off);
+        Path off = active.getSrcFile().resolveSibling(
+            active.getSrcFile().getFileName() + ".disabled");
+        try {
+          Files.move(active.getSrcFile(), off);
+        } catch (IOException e) {
+          log.error("Failed to disable plugin", e);
+          // In theory we could still unload the plugin even if the rename
+          // failed. However, it would be reloaded on the next server startup,
+          // which is probably not what the user expects.
+          continue;
+        }
 
         unloadPlugin(active);
         try {
-          FileSnapshot snapshot = FileSnapshot.save(off);
+          FileSnapshot snapshot = FileSnapshot.save(off.toFile());
           Plugin offPlugin = loadPlugin(name, off, snapshot);
           disabled.put(name, offPlugin);
         } catch (Throwable e) {
@@ -274,13 +283,17 @@
         }
 
         log.info(String.format("Enabling plugin %s", name));
-        String n = off.getSrcFile().getName();
+        String n = off.getSrcFile().toFile().getName();
         if (n.endsWith(".disabled")) {
           n = n.substring(0, n.lastIndexOf('.'));
         }
-        File on = new File(pluginsDir, n);
-        off.getSrcFile().renameTo(on);
-
+        Path on = pluginsDir.resolve(n);
+        try {
+          Files.move(off.getSrcFile(), on);
+        } catch (IOException e) {
+          log.error("Failed to move plugin " + name + " into place", e);
+          continue;
+        }
         disabled.remove(name);
         runPlugin(name, on, null);
       }
@@ -290,7 +303,7 @@
 
   @Override
   public synchronized void start() {
-    log.info("Loading plugins from " + pluginsDir.getAbsolutePath());
+    log.info("Loading plugins from " + pluginsDir.toAbsolutePath());
     srvInfoImpl.state = ServerInformation.State.STARTUP;
     rescan();
     srvInfoImpl.state = ServerInformation.State.RUNNING;
@@ -354,30 +367,30 @@
   }
 
   public synchronized void rescan() {
-    Multimap<String, File> pluginsFiles = prunePlugins(pluginsDir);
+    Multimap<String, Path> pluginsFiles = prunePlugins(pluginsDir);
     if (pluginsFiles.isEmpty()) {
       return;
     }
 
     syncDisabledPlugins(pluginsFiles);
 
-    Map<String, File> activePlugins = filterDisabled(pluginsFiles);
-    for (Map.Entry<String, File> entry : jarsFirstSortedPluginsSet(activePlugins)) {
+    Map<String, Path> activePlugins = filterDisabled(pluginsFiles);
+    for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
       String name = entry.getKey();
-      File file = entry.getValue();
-      String fileName = file.getName();
-      if (!isJsPlugin(fileName) && !serverPluginFactory.handles(file)) {
+      Path path = entry.getValue();
+      String fileName = path.getFileName().toString();
+      if (!isJsPlugin(fileName) && !serverPluginFactory.handles(path)) {
         log.warn("No Plugin provider was found that handles this file format: {}", fileName);
         continue;
       }
 
       FileSnapshot brokenTime = broken.get(name);
-      if (brokenTime != null && !brokenTime.isModified(file)) {
+      if (brokenTime != null && !brokenTime.isModified(path.toFile())) {
         continue;
       }
 
       Plugin active = running.get(name);
-      if (active != null && !active.isModified(file)) {
+      if (active != null && !active.isModified(path)) {
         continue;
       }
 
@@ -387,7 +400,7 @@
       }
 
       try {
-        Plugin loadedPlugin = runPlugin(name, file, active);
+        Plugin loadedPlugin = runPlugin(name, path, active);
         if (active == null && !loadedPlugin.isDisabled()) {
           log.info(String.format("Loaded plugin %s, version %s",
               loadedPlugin.getName(), loadedPlugin.getVersion()));
@@ -400,31 +413,28 @@
     cleanInBackground();
   }
 
-  private void addAllEntries(Map<String, File> from,
-      TreeSet<Entry<String, File>> to) {
-    Iterator<Entry<String, File>> it = from.entrySet().iterator();
+  private void addAllEntries(Map<String, Path> from,
+      TreeSet<Entry<String, Path>> to) {
+    Iterator<Entry<String, Path>> it = from.entrySet().iterator();
     while (it.hasNext()) {
-      Entry<String,File> entry = it.next();
+      Entry<String,Path> entry = it.next();
       to.add(new AbstractMap.SimpleImmutableEntry<>(
           entry.getKey(), entry.getValue()));
     }
   }
 
-  private TreeSet<Entry<String, File>> jarsFirstSortedPluginsSet(
-      Map<String, File> activePlugins) {
-    TreeSet<Entry<String, File>> sortedPlugins =
-        Sets.newTreeSet(new Comparator<Entry<String, File>>() {
+  private TreeSet<Entry<String, Path>> jarsFirstSortedPluginsSet(
+      Map<String, Path> activePlugins) {
+    TreeSet<Entry<String, Path>> sortedPlugins =
+        Sets.newTreeSet(new Comparator<Entry<String, Path>>() {
           @Override
-          public int compare(Entry<String, File> entry1,
-              Entry<String, File> entry2) {
-            String file1 = entry1.getValue().getName();
-            String file2 = entry2.getValue().getName();
-            int cmp = file1.compareTo(file2);
-            if (file1.endsWith(".jar")) {
-              return (file2.endsWith(".jar") ? cmp : -1);
-            } else {
-              return (file2.endsWith(".jar") ? +1 : cmp);
-            }
+          public int compare(Entry<String, Path> e1, Entry<String, Path> e2) {
+            Path n1 = e1.getValue().getFileName();
+            Path n2 = e2.getValue().getFileName();
+            return ComparisonChain.start()
+                .compareTrueFirst(n1.endsWith(".jar"), n2.endsWith(".jar"))
+                .compare(n1, n2)
+                .result();
           }
         });
 
@@ -432,14 +442,14 @@
     return sortedPlugins;
   }
 
-  private void syncDisabledPlugins(Multimap<String, File> jars) {
+  private void syncDisabledPlugins(Multimap<String, Path> jars) {
     stopRemovedPlugins(jars);
     dropRemovedDisabledPlugins(jars);
   }
 
-  private Plugin runPlugin(String name, File plugin, Plugin oldPlugin)
+  private Plugin runPlugin(String name, Path plugin, Plugin oldPlugin)
       throws PluginInstallException {
-    FileSnapshot snapshot = FileSnapshot.save(plugin);
+    FileSnapshot snapshot = FileSnapshot.save(plugin.toFile());
     try {
       Plugin newPlugin = loadPlugin(name, plugin, snapshot);
       if (newPlugin.getCleanupHandle() != null) {
@@ -479,11 +489,11 @@
     }
   }
 
-  private void stopRemovedPlugins(Multimap<String, File> jars) {
+  private void stopRemovedPlugins(Multimap<String, Path> jars) {
     Set<String> unload = Sets.newHashSet(running.keySet());
-    for (Map.Entry<String, Collection<File>> entry : jars.asMap().entrySet()) {
-      for (File file : entry.getValue()) {
-        if (!file.getName().endsWith(".disabled")) {
+    for (Map.Entry<String, Collection<Path>> entry : jars.asMap().entrySet()) {
+      for (Path path : entry.getValue()) {
+        if (!path.getFileName().toString().endsWith(".disabled")) {
           unload.remove(entry.getKey());
         }
       }
@@ -493,11 +503,11 @@
     }
   }
 
-  private void dropRemovedDisabledPlugins(Multimap<String, File> jars) {
+  private void dropRemovedDisabledPlugins(Multimap<String, Path> jars) {
     Set<String> unload = Sets.newHashSet(disabled.keySet());
-    for (Map.Entry<String, Collection<File>> entry : jars.asMap().entrySet()) {
-      for (File file : entry.getValue()) {
-        if (file.getName().endsWith(".disabled")) {
+    for (Map.Entry<String, Collection<Path>> entry : jars.asMap().entrySet()) {
+      for (Path path : entry.getValue()) {
+        if (path.getFileName().toString().endsWith(".disabled")) {
           unload.remove(entry.getKey());
         }
       }
@@ -528,8 +538,8 @@
     }
   }
 
-  public static String nameOf(File plugin) {
-    return nameOf(plugin.getName());
+  public static String nameOf(Path plugin) {
+    return nameOf(plugin.getFileName().toString());
   }
 
   private static String nameOf(String name) {
@@ -545,21 +555,21 @@
     return 0 < ext ? name.substring(ext) : "";
   }
 
-  private Plugin loadPlugin(String name, File srcPlugin, FileSnapshot snapshot)
+  private Plugin loadPlugin(String name, Path srcPlugin, FileSnapshot snapshot)
       throws InvalidPluginException {
-    String pluginName = srcPlugin.getName();
+    String pluginName = srcPlugin.getFileName().toString();
     if (isJsPlugin(pluginName)) {
       return loadJsPlugin(name, srcPlugin, snapshot);
     } else if (serverPluginFactory.handles(srcPlugin)) {
       return loadServerPlugin(srcPlugin, snapshot);
     } else {
       throw new InvalidPluginException(String.format(
-          "Unsupported plugin type: %s", srcPlugin.getName()));
+          "Unsupported plugin type: %s", srcPlugin.getFileName()));
     }
   }
 
-  private File getPluginDataDir(String name) {
-    return new File(dataDir, name);
+  private Path getPluginDataDir(String name) {
+    return dataDir.resolve(name);
   }
 
   private String getPluginCanonicalWebUrl(String name) {
@@ -569,11 +579,11 @@
     return url;
   }
 
-  private Plugin loadJsPlugin(String name, File srcJar, FileSnapshot snapshot) {
+  private Plugin loadJsPlugin(String name, Path srcJar, FileSnapshot snapshot) {
     return new JsPlugin(name, srcJar, pluginUserFactory.create(name), snapshot);
   }
 
-  private ServerPlugin loadServerPlugin(File scriptFile,
+  private ServerPlugin loadServerPlugin(Path scriptFile,
       FileSnapshot snapshot) throws InvalidPluginException {
     String name = serverPluginFactory.getPluginName(scriptFile);
     return serverPluginFactory.get(scriptFile, snapshot, new PluginDescription(
@@ -597,15 +607,15 @@
 
   // Only one active plugin per plugin name can exist for each plugin name.
   // Filter out disabled plugins and transform the multimap to a map
-  private static Map<String, File> filterDisabled(
-      Multimap<String, File> pluginFiles) {
-    Map<String, File> activePlugins = Maps.newHashMapWithExpectedSize(
-        pluginFiles.keys().size());
-    for (String name : pluginFiles.keys()) {
-      for (File pluginFile : pluginFiles.asMap().get(name)) {
-        if (!pluginFile.getName().endsWith(".disabled")) {
+  private static Map<String, Path> filterDisabled(
+      Multimap<String, Path> pluginPaths) {
+    Map<String, Path> activePlugins = Maps.newHashMapWithExpectedSize(
+        pluginPaths.keys().size());
+    for (String name : pluginPaths.keys()) {
+      for (Path pluginPath : pluginPaths.asMap().get(name)) {
+        if (!pluginPath.getFileName().toString().endsWith(".disabled")) {
           assert(!activePlugins.containsKey(name));
-          activePlugins.put(name, pluginFile);
+          activePlugins.put(name, pluginPath);
         }
       }
     }
@@ -621,37 +631,40 @@
   //
   // NOTE: Bear in mind that the plugin name can be reassigned after load by the
   //       Server plugin provider.
-  public Multimap<String, File> prunePlugins(File pluginsDir) {
-    List<File> pluginFiles = scanFilesInPluginsDirectory(pluginsDir);
-    Multimap<String, File> map;
-    map = asMultimap(pluginFiles);
+  public Multimap<String, Path> prunePlugins(Path pluginsDir) {
+    List<Path> pluginPaths = scanPathsInPluginsDirectory(pluginsDir);
+    Multimap<String, Path> map;
+    map = asMultimap(pluginPaths);
     for (String plugin : map.keySet()) {
-      Collection<File> files = map.asMap().get(plugin);
+      Collection<Path> files = map.asMap().get(plugin);
       if (files.size() == 1) {
         continue;
       }
       // retrieve enabled plugins
-      Iterable<File> enabled = filterDisabledPlugins(
-          files);
+      Iterable<Path> enabled = filterDisabledPlugins(files);
       // If we have only one (the winner) plugin, nothing to do
       if (!Iterables.skip(enabled, 1).iterator().hasNext()) {
         continue;
       }
-      File winner = Iterables.getFirst(enabled, null);
+      Path winner = Iterables.getFirst(enabled, null);
       assert(winner != null);
       // Disable all loser plugins by renaming their file names to
       // "file.disabled" and replace the disabled files in the multimap.
-      Collection<File> elementsToRemove = Lists.newArrayList();
-      Collection<File> elementsToAdd = Lists.newArrayList();
-      for (File loser : Iterables.skip(enabled, 1)) {
+      Collection<Path> elementsToRemove = Lists.newArrayList();
+      Collection<Path> elementsToAdd = Lists.newArrayList();
+      for (Path loser : Iterables.skip(enabled, 1)) {
         log.warn(String.format("Plugin <%s> was disabled, because"
              + " another plugin <%s>"
              + " with the same name <%s> already exists",
              loser, winner, plugin));
-        File disabledPlugin = new File(loser + ".disabled");
+        Path disabledPlugin = Paths.get(loser + ".disabled");
         elementsToAdd.add(disabledPlugin);
         elementsToRemove.add(loser);
-        loser.renameTo(disabledPlugin);
+        try {
+          Files.move(loser, disabledPlugin);
+        } catch (IOException e) {
+          log.warn("Failed to fully disable plugin " + loser, e);
+        }
       }
       Iterables.removeAll(files, elementsToRemove);
       Iterables.addAll(files, elementsToAdd);
@@ -659,50 +672,52 @@
     return map;
   }
 
-  private List<File> scanFilesInPluginsDirectory(File pluginsDir) {
-    if (pluginsDir == null || !pluginsDir.exists()) {
+  private List<Path> scanPathsInPluginsDirectory(Path pluginsDir) {
+    if (pluginsDir == null || !Files.exists(pluginsDir)) {
       return Collections.emptyList();
     }
-    File[] matches = pluginsDir.listFiles(new FileFilter() {
+    DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
       @Override
-      public boolean accept(File pathname) {
-        String n = pathname.getName();
+      public boolean accept(Path entry) throws IOException {
+        String n = entry.getFileName().toString();
         return !n.startsWith(".last_")
             && !n.startsWith(".next_");
       }
-    });
-    if (matches == null) {
-      log.error("Cannot list " + pluginsDir.getAbsolutePath());
-      return Collections.emptyList();
+    };
+    try (DirectoryStream<Path> files
+        = Files.newDirectoryStream(pluginsDir, filter)) {
+      return ImmutableList.copyOf(files);
+    } catch (IOException e) {
+      log.error("Cannot list " + pluginsDir.toAbsolutePath(), e);
+      return ImmutableList.of();
     }
-    return Arrays.asList(matches);
   }
 
-  private static Iterable<File> filterDisabledPlugins(
-      Collection<File> files) {
-    return Iterables.filter(files, new Predicate<File>() {
+  private static Iterable<Path> filterDisabledPlugins(
+      Collection<Path> paths) {
+    return Iterables.filter(paths, new Predicate<Path>() {
       @Override
-      public boolean apply(File file) {
-        return !file.getName().endsWith(".disabled");
+      public boolean apply(Path p) {
+        return !p.getFileName().toString().endsWith(".disabled");
       }
     });
   }
 
-  public String getGerritPluginName(File srcFile) {
-    String fileName = srcFile.getName();
+  public String getGerritPluginName(Path srcPath) {
+    String fileName = srcPath.getFileName().toString();
     if (isJsPlugin(fileName)) {
       return fileName.substring(0, fileName.length() - 3);
     }
-    if (serverPluginFactory.handles(srcFile)) {
-      return serverPluginFactory.getPluginName(srcFile);
+    if (serverPluginFactory.handles(srcPath)) {
+      return serverPluginFactory.getPluginName(srcPath);
     }
     return null;
   }
 
-  private Multimap<String, File> asMultimap(List<File> plugins) {
-    Multimap<String, File> map = LinkedHashMultimap.create();
-    for (File srcFile : plugins) {
-      map.put(getPluginName(srcFile), srcFile);
+  private Multimap<String, Path> asMultimap(List<Path> plugins) {
+    Multimap<String, Path> map = LinkedHashMultimap.create();
+    for (Path srcPath : plugins) {
+      map.put(getPluginName(srcPath), srcPath);
     }
     return map;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index 0b037fb..28d57b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -17,25 +17,19 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
-import com.google.gerrit.extensions.annotations.PluginData;
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.server.PluginUser;
 import com.google.gerrit.server.util.RequestContext;
-import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Module;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
 
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
@@ -59,7 +53,7 @@
 
   private final Manifest manifest;
   private final PluginContentScanner scanner;
-  private final File dataDir;
+  private final Path dataDir;
   private final String pluginCanonicalWebUrl;
   private final ClassLoader classLoader;
   private Class<? extends Module> sysModule;
@@ -75,12 +69,13 @@
   public ServerPlugin(String name,
       String pluginCanonicalWebUrl,
       PluginUser pluginUser,
-      File srcJar,
+      Path srcJar,
       FileSnapshot snapshot,
       PluginContentScanner scanner,
-      File dataDir,
+      Path dataDir,
       ClassLoader classLoader) throws InvalidPluginException {
-    super(name, srcJar, pluginUser, snapshot, Plugin.getApiType(getPluginManifest(scanner)));
+    super(name, srcJar, pluginUser, snapshot,
+        Plugin.getApiType(getPluginManifest(scanner)));
     this.pluginCanonicalWebUrl = pluginCanonicalWebUrl;
     this.scanner = scanner;
     this.dataDir = dataDir;
@@ -127,10 +122,18 @@
     return (Class<? extends Module>) clazz;
   }
 
-  File getSrcJar() {
+  Path getSrcJar() {
     return getSrcFile();
   }
 
+  Path getDataDir() {
+    return dataDir;
+  }
+
+  String getPluginCanonicalWebUrl() {
+    return pluginCanonicalWebUrl;
+  }
+
   private static Manifest getPluginManifest(PluginContentScanner scanner)
       throws InvalidPluginException {
     try {
@@ -229,45 +232,11 @@
   }
 
   private Injector newRootInjector(final PluginGuiceEnvironment env) {
-    List<Module> modules = Lists.newArrayListWithCapacity(4);
+    List<Module> modules = Lists.newArrayListWithCapacity(2);
     if (getApiType() == ApiType.PLUGIN) {
       modules.add(env.getSysModule());
     }
-    modules.add(new AbstractModule() {
-      @Override
-      protected void configure() {
-        bind(PluginUser.class).toInstance(getPluginUser());
-        bind(String.class)
-          .annotatedWith(PluginName.class)
-          .toInstance(getName());
-        bind(String.class)
-          .annotatedWith(PluginCanonicalWebUrl.class)
-          .toInstance(pluginCanonicalWebUrl);
-
-        bind(File.class)
-          .annotatedWith(PluginData.class)
-          .toProvider(new Provider<File>() {
-            private volatile boolean ready;
-
-            @Override
-            public File get() {
-              if (!ready) {
-                synchronized (dataDir) {
-                  if (!ready) {
-                    if (!dataDir.exists() && !dataDir.mkdirs()) {
-                      throw new ProvisionException(String.format(
-                          "Cannot create %s for plugin %s",
-                          dataDir.getAbsolutePath(), getName()));
-                    }
-                    ready = true;
-                  }
-                }
-              }
-              return dataDir;
-            }
-          });
-      }
-    });
+    modules.add(new ServerPluginInfoModule(this));
     return Guice.createInjector(modules);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
new file mode 100644
index 0000000..b0e9453
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginInfoModule.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.PluginUser;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.ProvisionException;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+class ServerPluginInfoModule extends AbstractModule {
+  private final ServerPlugin plugin;
+  private final Path dataDir;
+
+  private volatile boolean ready;
+
+  ServerPluginInfoModule(ServerPlugin plugin) {
+    this.plugin = plugin;
+    this.dataDir = plugin.getDataDir();
+  }
+
+  @Override
+  protected void configure() {
+    bind(PluginUser.class).toInstance(plugin.getPluginUser());
+    bind(String.class)
+      .annotatedWith(PluginName.class)
+      .toInstance(plugin.getName());
+    bind(String.class)
+      .annotatedWith(PluginCanonicalWebUrl.class)
+      .toInstance(plugin.getPluginCanonicalWebUrl());
+  }
+
+  @Provides
+  @PluginData
+  Path getPluginData() {
+    if (!ready) {
+      synchronized (dataDir) {
+        if (!ready) {
+          try {
+            Files.createDirectories(dataDir);
+          } catch (IOException e) {
+            throw new ProvisionException(String.format(
+                "Cannot create %s for plugin %s",
+                dataDir.toAbsolutePath(), plugin.getName()), e);
+          }
+          ready = true;
+        }
+      }
+    }
+    return dataDir;
+  }
+
+  @Provides
+  @PluginData
+  File getPluginDataAsFile(@PluginData Path pluginData) {
+    return pluginData.toFile();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginProvider.java
index 37fed9b..bc2432b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPluginProvider.java
@@ -19,7 +19,7 @@
 
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
-import java.io.File;
+import java.nio.file.Path;
 
 /**
  * Provider of one Server plugin from one external file
@@ -40,7 +40,7 @@
   public class PluginDescription {
     public final PluginUser user;
     public final String canonicalUrl;
-    public final File dataDir;
+    public final Path dataDir;
 
     /**
      * Creates a new PluginDescription for ServerPluginProvider.
@@ -49,7 +49,7 @@
      * @param canonicalUrl plugin root Web URL
      * @param dataDir directory for plugin data
      */
-    public PluginDescription(PluginUser user, String canonicalUrl, File dataDir) {
+    public PluginDescription(PluginUser user, String canonicalUrl, Path dataDir) {
       this.user = user;
       this.canonicalUrl = canonicalUrl;
       this.dataDir = dataDir;
@@ -59,39 +59,39 @@
   /**
    * Declares the availability to manage an external file or directory
    *
-   * @param srcFile the external file or directory
+   * @param srcPath the external file or directory
    * @return true if file or directory can be loaded into a Server Plugin
    */
-  boolean handles(File srcFile);
+  boolean handles(Path srcPath);
 
   /**
    * Returns the plugin name of an external file or directory
    *
-   * Should be called only if {@link #handles(File) handles(srcFile)}
+   * Should be called only if {@link #handles(Path) handles(srcFile)}
    * returns true and thus srcFile is a supported plugin format.
    * An IllegalArgumentException is thrown otherwise as srcFile
    * is not a valid file format for extracting its plugin name.
    *
-   * @param srcFile external file or directory
+   * @param srcPath external file or directory
    * @return plugin name
    */
-  String getPluginName(File srcFile);
+  String getPluginName(Path srcPath);
 
   /**
    * Loads an external file or directory into a Server plugin.
    *
-   * Should be called only if {@link #handles(File) handles(srcFile)}
+   * Should be called only if {@link #handles(Path) handles(srcFile)}
    * returns true and thus srcFile is a supported plugin format.
    * An IllegalArgumentException is thrown otherwise as srcFile
    * is not a valid file format for extracting its plugin name.
    *
-   * @param srcFile external file or directory
+   * @param srcPath external file or directory
    * @param snapshot snapshot of the external file
    * @param pluginDescriptor descriptor of the ServerPlugin to load
    * @throws InvalidPluginException if plugin is supposed to be handled
    *         but cannot be loaded for any other reason
    */
-  ServerPlugin get(File srcFile, FileSnapshot snapshot,
+  ServerPlugin get(Path srcPath, FileSnapshot snapshot,
       PluginDescription pluginDescriptor) throws InvalidPluginException;
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
index 0e8bd87..afdc5b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
@@ -22,7 +22,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,27 +38,26 @@
   }
 
   @Override
-  public ServerPlugin get(File srcFile, FileSnapshot snapshot,
+  public ServerPlugin get(Path srcPath, FileSnapshot snapshot,
       PluginDescription pluginDescription) throws InvalidPluginException {
-    return providerOf(srcFile).get(srcFile, snapshot, pluginDescription);
+    return providerOf(srcPath).get(srcPath, snapshot, pluginDescription);
   }
 
   @Override
-  public String getPluginName(File srcFile) {
-    return providerOf(srcFile).getPluginName(srcFile);
+  public String getPluginName(Path srcPath) {
+    return providerOf(srcPath).getPluginName(srcPath);
   }
 
   @Override
-  public boolean handles(File srcFile) {
-    List<ServerPluginProvider> providers =
-        providersForHandlingPlugin(srcFile);
+  public boolean handles(Path srcPath) {
+    List<ServerPluginProvider> providers = providersForHandlingPlugin(srcPath);
     switch (providers.size()) {
       case 1:
         return true;
       case 0:
         return false;
       default:
-        throw new MultipleProvidersForPluginException(srcFile, providers);
+        throw new MultipleProvidersForPluginException(srcPath, providers);
     }
   }
 
@@ -67,27 +66,27 @@
     return "gerrit";
   }
 
-  private ServerPluginProvider providerOf(File srcFile) {
+  private ServerPluginProvider providerOf(Path srcPath) {
     List<ServerPluginProvider> providers =
-        providersForHandlingPlugin(srcFile);
+        providersForHandlingPlugin(srcPath);
     switch (providers.size()) {
       case 1:
         return providers.get(0);
       case 0:
         throw new IllegalArgumentException(
             "No ServerPluginProvider found/loaded to handle plugin file "
-                + srcFile.getAbsolutePath());
+                + srcPath.toAbsolutePath());
       default:
-        throw new MultipleProvidersForPluginException(srcFile, providers);
+        throw new MultipleProvidersForPluginException(srcPath, providers);
     }
   }
 
   private List<ServerPluginProvider> providersForHandlingPlugin(
-      final File srcFile) {
+      final Path srcPath) {
     List<ServerPluginProvider> providers = new ArrayList<>();
     for (ServerPluginProvider serverPluginProvider : serverPluginProviders) {
-      boolean handles = serverPluginProvider.handles(srcFile);
-      log.debug("File {} handled by {} ? => {}", srcFile,
+      boolean handles = serverPluginProvider.handles(srcPath);
+      log.debug("File {} handled by {} ? => {}", srcPath,
           serverPluginProvider.getProviderPluginName(), handles);
       if (handles) {
         providers.add(serverPluginProvider);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index daf1d4d..8829ac3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -32,14 +32,14 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.PersonIdent;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Collections;
 
 /** Creates the current database schema and populates initial code rows. */
 public class SchemaCreator {
   private final @SitePath
-  File site_path;
+  Path site_path;
 
   private final AllProjectsCreator allProjectsCreator;
   private final AllUsersCreator allUsersCreator;
@@ -55,10 +55,10 @@
       AllUsersCreator auc,
       @GerritPersonIdent PersonIdent au,
       DataSourceType dst) {
-    this(site.site_path, ap, auc, au, dst);
+    this(site.site_path.toPath(), ap, auc, au, dst);
   }
 
-  public SchemaCreator(@SitePath File site,
+  public SchemaCreator(@SitePath Path site,
       AllProjectsCreator ap,
       AllUsersCreator auc,
       @GerritPersonIdent PersonIdent au,
@@ -117,9 +117,9 @@
 
     final SystemConfig s = SystemConfig.create();
     try {
-      s.sitePath = site_path.getCanonicalPath();
+      s.sitePath = site_path.toRealPath().normalize().toString();
     } catch (IOException e) {
-      s.sitePath = site_path.getAbsolutePath();
+      s.sitePath = site_path.toAbsolutePath().normalize().toString();
     }
     c.systemConfig().insert(Collections.singleton(s));
     return s;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
index 591601d..acfbba2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersionCheck.java
@@ -64,7 +64,7 @@
           throw new ProvisionException("Unsupported schema version "
               + currentVer.versionNbr + "; expected schema version " + expectedVer
               + ".  Run init to upgrade:\n"
-              + "$ java -jar " + site.gerrit_war.getAbsolutePath() + " init -d "
+              + "$ java -jar " + site.gerrit_war.toAbsolutePath() + " init -d "
               + site.site_path.getAbsolutePath());
         } else if (currentVer.versionNbr > expectedVer) {
           throw new ProvisionException("Unsupported schema version "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
index b852217..7665c64 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/DefaultSecureStore.java
@@ -26,6 +26,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -35,8 +36,8 @@
 
   @Inject
   DefaultSecureStore(SitePaths site) {
-    File secureConfig = new File(site.etc_dir, "secure.config");
-    sec = new FileBasedConfig(secureConfig, FS.DETECTED);
+    Path secureConfig = site.etc_dir.resolve("secure.config");
+    sec = new FileBasedConfig(secureConfig.toFile(), FS.DETECTED);
     try {
       sec.load();
     } catch (Exception e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java
index e830590..99127d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreProvider.java
@@ -26,14 +26,14 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
+import java.nio.file.Path;
 
 @Singleton
 public class SecureStoreProvider implements Provider<SecureStore> {
   private static final Logger log = LoggerFactory
       .getLogger(SecureStoreProvider.class);
 
-  private final File libdir;
+  private final Path libdir;
   private final Injector injector;
   private final String className;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
index ba31f56..d4b8457 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
@@ -33,8 +33,8 @@
 import org.eclipse.jgit.lib.Config;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 
 @Singleton
 public class SystemLog {
@@ -55,12 +55,12 @@
     return Strings.isNullOrEmpty(System.getProperty(LOG4J_CONFIGURATION));
   }
 
-  public static Appender createAppender(File logdir, String name, Layout layout) {
+  public static Appender createAppender(Path logdir, String name, Layout layout) {
     final DailyRollingFileAppender dst = new DailyRollingFileAppender();
     dst.setName(name);
     dst.setLayout(layout);
     dst.setEncoding("UTF-8");
-    dst.setFile(new File(resolve(logdir), name).getPath());
+    dst.setFile(resolve(logdir).resolve(name).toString());
     dst.setImmediateFlush(true);
     dst.setAppend(true);
     dst.setErrorHandler(new DieErrorHandler());
@@ -90,11 +90,11 @@
     return async;
   }
 
-  private static File resolve(final File logs_dir) {
+  private static Path resolve(Path p) {
     try {
-      return logs_dir.getCanonicalFile();
+      return p.toRealPath().normalize();
     } catch (IOException e) {
-      return logs_dir.getAbsoluteFile();
+      return p.toAbsolutePath().normalize();
     }
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
index 5fdecf0..4808091 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
@@ -28,85 +28,86 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 public class SitePathsTest {
   @Test
   public void testCreate_NotExisting() throws IOException {
-    final File root = random();
+    final Path root = random();
     final SitePaths site = new SitePaths(root);
     assertTrue(site.isNew);
-    assertEquals(root, site.site_path);
-    assertEquals(new File(root, "etc"), site.etc_dir);
+    assertEquals(root.toFile(), site.site_path);
+    assertEquals(root.resolve("etc"), site.etc_dir);
   }
 
   @Test
   public void testCreate_Empty() throws IOException {
-    final File root = random();
+    final Path root = random();
     try {
-      assertTrue(root.mkdir());
+      Files.createDirectory(root);
 
       final SitePaths site = new SitePaths(root);
       assertTrue(site.isNew);
-      assertEquals(root, site.site_path);
+      assertEquals(root.toFile(), site.site_path);
     } finally {
-      root.delete();
+      Files.delete(root);
     }
   }
 
   @Test
   public void testCreate_NonEmpty() throws IOException {
-    final File root = random();
-    final File txt = new File(root, "test.txt");
+    final Path root = random();
+    final Path txt = root.resolve("test.txt");
     try {
-      assertTrue(root.mkdir());
-      assertTrue(txt.createNewFile());
+      Files.createDirectory(root);
+      Files.createFile(txt);
 
       final SitePaths site = new SitePaths(root);
       assertFalse(site.isNew);
-      assertEquals(root, site.site_path);
+      assertEquals(root.toFile(), site.site_path);
     } finally {
-      txt.delete();
-      root.delete();
+      Files.delete(txt);
+      Files.delete(root);
     }
   }
 
   @Test
   public void testCreate_NotDirectory() throws IOException {
-    final File root = random();
+    final Path root = random();
     try {
-      assertTrue(root.createNewFile());
+      Files.createFile(root);
       try {
         new SitePaths(root);
         fail("Did not throw exception");
       } catch (FileNotFoundException e) {
-        assertEquals("Not a directory: " + root.getPath(), e.getMessage());
+        assertEquals("Not a directory: " + root, e.getMessage());
       }
     } finally {
-      root.delete();
+      Files.delete(root);
     }
   }
 
   @Test
   public void testResolve() throws IOException {
-    final File root = random();
+    final Path root = random();
     final SitePaths site = new SitePaths(root);
 
     assertNull(site.resolve(null));
     assertNull(site.resolve(""));
 
     assertNotNull(site.resolve("a"));
-    assertEquals(new File(root, "a").getCanonicalFile(), site.resolve("a"));
+    assertEquals(root.resolve("a").toAbsolutePath().normalize().toFile(),
+        site.resolve("a"));
 
     final String pfx = HostPlatform.isWin32() ? "C:/" : "/";
     assertNotNull(site.resolve(pfx + "a"));
     assertEquals(new File(pfx + "a").getCanonicalFile(), site.resolve(pfx + "a"));
   }
 
-  private static File random() throws IOException {
-    File tmp = File.createTempFile("gerrit_test_", "_site");
-    if (!tmp.delete()) {
-      throw new IOException("Cannot create " + tmp.getPath());
-    }
+  private static Path random() throws IOException {
+    Path tmp = Files.createTempFile("gerrit_test_", "_site");
+    Files.deleteIfExists(tmp);
     return tmp;
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index 8686fe6..141c8e3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -43,9 +43,10 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 import java.util.UUID;
 
@@ -67,7 +68,7 @@
       IOException {
     db.create();
 
-    final File site = new File(UUID.randomUUID().toString());
+    final Path site = Paths.get(UUID.randomUUID().toString());
     final SitePaths paths = new SitePaths(site);
     SchemaUpdater u = Guice.createInjector(new FactoryModule() {
       @Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 72495b3..f33cfb2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -69,11 +69,12 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 
-import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 public class InMemoryModule extends FactoryModule {
   public static Config newDefaultConfig() {
@@ -125,7 +126,8 @@
 
     bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
 
-    bind(File.class).annotatedWith(SitePath.class).toInstance(new File("."));
+    // TODO(dborowitz): Use jimfs.
+    bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toInstance(
         new InetSocketAddress(InetAddresses.forString("127.0.0.1"), 1234));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 93d2e5b..ab3b446 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.sshd;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
@@ -33,10 +36,10 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
 import java.security.KeyPair;
 import java.security.PublicKey;
 import java.util.Collection;
@@ -170,56 +173,50 @@
   }
 
   private static class PeerKeyCache {
-    private final File path;
+    private final Path path;
     private final long modified;
     final Set<PublicKey> keys;
 
-    PeerKeyCache(final File path) {
+    PeerKeyCache(Path path) {
       this.path = path;
-      this.modified = path.lastModified();
+      this.modified = FileUtil.lastModified(path);
       this.keys = read(path);
     }
 
-    private static Set<PublicKey> read(File path) {
-      try {
-        final BufferedReader br = new BufferedReader(new FileReader(path));
-        try {
-          final Set<PublicKey> keys = new HashSet<>();
-          String line;
-          while ((line = br.readLine()) != null) {
-            line = line.trim();
-            if (line.startsWith("#") || line.isEmpty()) {
-              continue;
-            }
-
-            try {
-              byte[] bin = Base64.decodeBase64(line.getBytes("ISO-8859-1"));
-              keys.add(new Buffer(bin).getRawPublicKey());
-            } catch (RuntimeException e) {
-              logBadKey(path, line, e);
-            } catch (SshException e) {
-              logBadKey(path, line, e);
-            }
+    private static Set<PublicKey> read(Path path) {
+      try (BufferedReader br = Files.newBufferedReader(path, UTF_8)) {
+        final Set<PublicKey> keys = new HashSet<>();
+        String line;
+        while ((line = br.readLine()) != null) {
+          line = line.trim();
+          if (line.startsWith("#") || line.isEmpty()) {
+            continue;
           }
-          return Collections.unmodifiableSet(keys);
-        } finally {
-          br.close();
-        }
-      } catch (FileNotFoundException noFile) {
-        return Collections.emptySet();
 
+          try {
+            byte[] bin = Base64.decodeBase64(line.getBytes("ISO-8859-1"));
+            keys.add(new Buffer(bin).getRawPublicKey());
+          } catch (RuntimeException e) {
+            logBadKey(path, line, e);
+          } catch (SshException e) {
+            logBadKey(path, line, e);
+          }
+        }
+        return Collections.unmodifiableSet(keys);
+      } catch (NoSuchFileException noFile) {
+        return Collections.emptySet();
       } catch (IOException err) {
         log.error("Cannot read " + path, err);
         return Collections.emptySet();
       }
     }
 
-    private static void logBadKey(File path, String line, Exception e) {
+    private static void logBadKey(Path path, String line, Exception e) {
       log.warn("Invalid key in " + path + ":\n  " + line, e);
     }
 
     boolean isCurrent() {
-      return path.lastModified() == modified;
+      return modified == FileUtil.lastModified(path);
     }
 
     PeerKeyCache reload() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
index 241f853..3e6e2f5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java
@@ -24,7 +24,8 @@
 import org.apache.sshd.common.util.SecurityUtils;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 
-import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,29 +39,29 @@
 
   @Override
   public KeyPairProvider get() {
-    final File objKey = site.ssh_key;
-    final File rsaKey = site.ssh_rsa;
-    final File dsaKey = site.ssh_dsa;
+    Path objKey = site.ssh_key;
+    Path rsaKey = site.ssh_rsa;
+    Path dsaKey = site.ssh_dsa;
 
     final List<String> stdKeys = new ArrayList<>(2);
-    if (rsaKey.exists()) {
-      stdKeys.add(rsaKey.getAbsolutePath());
+    if (Files.exists(rsaKey)) {
+      stdKeys.add(rsaKey.toAbsolutePath().toString());
     }
-    if (dsaKey.exists()) {
-      stdKeys.add(dsaKey.getAbsolutePath());
+    if (Files.exists(dsaKey)) {
+      stdKeys.add(dsaKey.toAbsolutePath().toString());
     }
 
-    if (objKey.exists()) {
+    if (Files.exists(objKey)) {
       if (stdKeys.isEmpty()) {
         SimpleGeneratorHostKeyProvider p = new SimpleGeneratorHostKeyProvider();
-        p.setPath(objKey.getAbsolutePath());
+        p.setPath(objKey.toAbsolutePath().toString());
         return p;
 
       } else {
         // Both formats of host key exist, we don't know which format
         // should be authoritative. Complain and abort.
         //
-        stdKeys.add(objKey.getAbsolutePath());
+        stdKeys.add(objKey.toAbsolutePath().toString());
         throw new ProvisionException("Multiple host keys exist: " + stdKeys);
       }
 
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 81d8498..17e8286 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>2.11-SNAPSHOT</version>
+  <version>2.12-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
index 6bbbd8f..ea4a3ea 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -20,7 +20,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -47,21 +48,19 @@
   public void init() {
     try {
       if (sitePath != null) {
-        File site = new File(sitePath);
-        LOG.info(String.format("Initializing site at %s",
-            site.getAbsolutePath()));
+        Path site = Paths.get(sitePath);
+        LOG.info("Initializing site at " + site.toRealPath().normalize());
         new BaseInit(site, false, true, pluginsDistribution, pluginsToInstall).run();
         return;
       }
 
       try (Connection conn = connectToDb()) {
-        File site = getSiteFromReviewDb(conn);
+        Path site = getSiteFromReviewDb(conn);
         if (site == null && initPath != null) {
-          site = new File(initPath);
+          site = Paths.get(initPath);
         }
         if (site != null) {
-          LOG.info(String.format("Initializing site at %s",
-              site.getAbsolutePath()));
+          LOG.info("Initializing site at " + site.toRealPath().normalize());
           new BaseInit(site, new ReviewDbDataSourceProvider(), false, false,
               pluginsDistribution, pluginsToInstall).run();
         }
@@ -76,12 +75,12 @@
     return new ReviewDbDataSourceProvider().get().getConnection();
   }
 
-  private File getSiteFromReviewDb(Connection conn) {
+  private Path getSiteFromReviewDb(Connection conn) {
     try (Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(
           "SELECT site_path FROM system_config")) {
       if (rs.next()) {
-        return new File(rs.getString(1));
+        return Paths.get(rs.getString(1));
       }
     } catch (SQLException e) {
       return null;
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java
index b97df3f..60f389e 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SitePathFromSystemConfigProvider.java
@@ -22,12 +22,13 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 
-/** Provides {@link java.io.File} annotated with {@link SitePath}. */
-class SitePathFromSystemConfigProvider implements Provider<File> {
-  private final File path;
+/** Provides {@link Path} annotated with {@link SitePath}. */
+class SitePathFromSystemConfigProvider implements Provider<Path> {
+  private final Path path;
 
   @Inject
   SitePathFromSystemConfigProvider(SchemaFactory<ReviewDb> schemaFactory)
@@ -36,18 +37,18 @@
   }
 
   @Override
-  public File get() {
+  public Path get() {
     return path;
   }
 
-  private static File read(SchemaFactory<ReviewDb> schemaFactory)
+  private static Path read(SchemaFactory<ReviewDb> schemaFactory)
       throws OrmException {
     ReviewDb db = schemaFactory.open();
     try {
       List<SystemConfig> all = db.systemConfig().all().toList();
       switch (all.size()) {
         case 1:
-          return new File(all.get(0).sitePath);
+          return Paths.get(all.get(0).sitePath);
         case 0:
           throw new OrmException("system_config table is empty");
         default:
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 15e2daa..eb4c792 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -78,8 +78,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -101,7 +102,7 @@
   private static final Logger log =
       LoggerFactory.getLogger(WebAppInitializer.class);
 
-  private File sitePath;
+  private Path sitePath;
   private Injector dbInjector;
   private Injector cfgInjector;
   private Injector sysInjector;
@@ -122,7 +123,7 @@
     if (manager == null) {
       final String path = System.getProperty("gerrit.site_path");
       if (path != null) {
-        sitePath = new File(path);
+        sitePath = Paths.get(path);
       }
 
       if (System.getProperty("gerrit.init") != null) {
@@ -209,7 +210,7 @@
       Module sitePathModule = new AbstractModule() {
         @Override
         protected void configure() {
-          bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+          bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
         }
       };
       modules.add(sitePathModule);
@@ -261,7 +262,7 @@
       modules.add(new AbstractModule() {
         @Override
         protected void configure() {
-          bind(File.class).annotatedWith(SitePath.class).toProvider(
+          bind(Path.class).annotatedWith(SitePath.class).toProvider(
               SitePathFromSystemConfigProvider.class).in(SINGLETON);
         }
       });
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index 4c295fd..cbcc3ca 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit 4c295fd3829725af769dc3c37db9df1c562e24a3
+Subproject commit cbcc3cac14604ff2194473b38d268dbcfa8f1dcc
diff --git a/plugins/replication b/plugins/replication
index 3973b31..2b86260 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 3973b31f3c86cc5401ef2dfac5ac82f25d79e432
+Subproject commit 2b862605ef89a3c6858ace37ee048b526f8dcd35