private static Optional<String> getDownloadedOrNextUrl(final String urlString, final Path path, final String userAgent)
            throws IOException {
        logger.debug("Requesting GET {}", urlString);
        final URL url;
        try {
            url = new URL(urlString);
        } catch (final MalformedURLException ex) {
            throw new IOException("Invalid URL: " + urlString, ex);
        }

        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        try {
            // Need to follow redirects for GET, not HEAD, here because:
            // * https://dl.bintray.com/... returns 200 for HEAD, and 302 for GET
            // * https://github-production-release-asset-XXXXXX.s3.amazonaws.com/... returns 200 for GET, and 403 for HEAD
            connection.setInstanceFollowRedirects(false);

            connection.setRequestMethod("GET");
            connection.setRequestProperty("Accept", "*/*");

            // Cloudflare requires User-Agent.
            connection.setRequestProperty("User-Agent", userAgent);

            connection.connect();

            final int statusCode = connection.getResponseCode();
            logger.debug("Received HTTP status code {} from GET {}", statusCode, urlString);
            switch (statusCode) {
                case HttpURLConnection.HTTP_MOVED_PERM:
                case HttpURLConnection.HTTP_MOVED_TEMP:
                    final String location = connection.getHeaderField("Location");
                    if (location == null) {
                        throw new IOException("No Location header for HTTP status code " + statusCode);
                    }
                    return Optional.of(location);
                case HttpURLConnection.HTTP_OK:
                    final InputStream downloadStream = connection.getInputStream();
                    logger.info("Downloading from {} into {}", urlString, path.toString());
                    // It should be okay to replace the temporary file created by |Files.createTempFile|.
                    Files.copy(downloadStream, path, StandardCopyOption.REPLACE_EXISTING);
                    logger.info("Downloaded");
                    return Optional.empty();
                default:
                    final String responseMessage;
                    try {
                        responseMessage = connection.getResponseMessage();
                    } catch (final IOException ex) {
                        throw new IOException("Unexpected HTTP status code " + statusCode, ex);
                    }
                    throw new IOException("Unexpected HTTP status code " + statusCode + " with message: " + responseMessage);
            }
        } finally {
            connection.disconnect();
        }
    }
Runs the selfupdate subcommand of Embulk.

<p>It uses {@link java.net.HttpURLConnection} so that CLI classes do not need additional dependedcies.

<p>TODO: Support HTTP(S) proxy. The original Ruby version did not support as well, though.
embulk-core/src/main/java/org/embulk/cli/SelfUpdate.java