Simple tool to save a web page's source. A result of trying to learn and apply an MVC approach. The FXML is generated via Scene Builder, but included for completion.

I welcome any general feedback on how to improve anything but in particular:

  1. There are many exceptions, current impression is that current handling is not ideal. What is the conventional wisdom on handling a plethora of io/connection errors?
  2. API and methods utilized in reading and writing were changed several times. Thanks to @rolfl's answer to a related question, currently refactored to employ Java 8 streams, is this the most appropriate for purpose? Can it be cleaner? Faster?
  3. Any conventions that should be followed, especially in regards to MVC, FXML.

The following is the source, with a line in the main class commented out so this may be executed if desired.

Main Class:

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;

public class HTMLSave extends Application {
    public static void main(String[] args) {

    public void start(Stage stage) {
        Parent root = null;
        try {
            root = FXMLLoader.load(getClass().getResource("HTML_Save.fxml"));
        } catch (IOException ioe) {
            System.err.println("Could not load FXML");

        Scene scene = new Scene(root, Color.TRANSPARENT);

        stage.setTitle("HTML Saver");
        //stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));

Controller Class:

import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;

public class HTMLSaveController implements Initializable {
    private Button saveButton;
    private Button openButton;
    private TextField htmlField;
    private TextField filenameField;
    private ProgressBar progressBar;

    private String destinationFile;

    public void initialize(URL location, ResourceBundle resources) {
        System.out.println("HTML Saver view successfully loaded.");

    public void handleSaveRequest() {
        String filename = filenameField.getText();
        String url = htmlField.getText();
        if (url.isEmpty()) {
            errorAlert("URL missing", "Please paste in website address");
        } else if (filename.isEmpty()) {
            errorAlert("No filename", "Please enter a file name to save html");
        } else {
            destinationFile = filename + ".html";
            try (Writer destination = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destinationFile), "UTF-8"))) {
                savePageSource(url, destination);
            } catch (IOException ioe) {
                System.err.println("Could not write to " + filename + '.');

    public void handleOpenRequest() {
        try {
            Desktop.getDesktop().open(new File(destinationFile));
        } catch (IOException ioe) {
            System.err.println(destinationFile + " not found.");

    private void errorAlert(String message, String infoMessage) {
        Alert alert = new Alert(AlertType.ERROR);

    private void savePageSource(String url, Writer destination) throws IOException {
        Task saveTask = new Task() {
            protected Void call() throws IOException {
                updateProgress(0, 100);

                try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) {

                } catch (IOException ioe) {
                    throw new UncheckedIOException(ioe);
                updateProgress(1, 1);
                return null;

        Thread savingThread = new Thread(saveTask);
        try {
        } catch (InterruptedException ie) {


.label {
    -fx-text-fill: rgb(166, 226, 46);
    -fx-font-weight: bold;
.button {
    -fx-text-fill: rgb(166, 226, 46);
    -fx-background-color: rgb(39, 40, 34);
    -fx-border-radius: 20;
    -fx-background-radius: 20;
    -fx-padding: 5;
.button:hover {
    -fx-text-fill: rgb(249, 38, 114);
    -fx-font-family: "Courier New";
    -fx-background: rgb(61, 61, 59);
    -fx-base: rgb(39, 40, 34);
.text-field:focused {
    -fx-background-color: #a9a9a9 , white , white;
    -fx-background-insets: 0 -1 -1 -1, 0 0 0 0, 0 -1 3 -1;
.progress-bar > .bar {
    -fx-background-color: linear-gradient(
        from 0px .75em to .75em 0px,
        rgb(253, 151, 31) 0%,
        rgb(253, 151, 31) 49%,
        derive(rgb(253, 151, 31), 30%) 50%,
        derive(rgb(253, 151, 31), 30%) 99%


<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="174.0" prefWidth="474.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="HTMLSaveController">
    <ColumnConstraints hgrow="SOMETIMES" maxWidth="309.0" minWidth="10.0" prefWidth="203.0" />
    <ColumnConstraints hgrow="SOMETIMES" maxWidth="398.0" minWidth="10.0" prefWidth="271.0" />
    <RowConstraints maxHeight="121.0" minHeight="10.0" prefHeight="114.0" vgrow="SOMETIMES" />
    <RowConstraints maxHeight="181.0" minHeight="10.0" prefHeight="129.0" vgrow="SOMETIMES" />
    <RowConstraints maxHeight="293.0" minHeight="10.0" prefHeight="121.0" vgrow="SOMETIMES" />
      <Button fx:id="saveButton" defaultButton="true" mnemonicParsing="false" onAction="#handleSaveRequest" prefHeight="39.0" prefWidth="73.0" text="Save" textFill="#36d721" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="2" GridPane.valignment="TOP">
            <Font size="18.0" />
            <Cursor fx:constant="HAND" />
      <Label text="Paste Address: ">
            <Font size="20.0" />
      <Label text="Enter Filename: " GridPane.rowIndex="1">
            <Font size="20.0" />
      <TextField fx:id="htmlField" GridPane.columnIndex="1" />
      <TextField fx:id="filenameField" GridPane.columnIndex="1" GridPane.rowIndex="1" />
      <VBox prefHeight="123.0" prefWidth="205.0" GridPane.rowIndex="2">
            <Button fx:id="openButton" disable="true" mnemonicParsing="false" onAction="#handleOpenRequest" prefHeight="39.0" prefWidth="81.0" text="Open" textFill="#6f2929">
                  <Font size="18.0" />
                  <Cursor fx:constant="HAND" />
            <ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0">
                  <Cursor fx:constant="DEFAULT" />

The result:

Application sample run


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.