Todo List TUI in Java

This one is practical on day one. Build a todo manager you can use while studying.

This version is complete and runnable in one Java file.

Read the mental model first

Download source file

Quick Run (from repo root)

1cd Site/static/code/examples/java
2javac TodoCli.java
3java TodoCli

Features

  • add tasks
  • list tasks
  • mark tasks done
  • remove tasks
  • save to disk automatically

Code (Single Public Class)

  1import java.io.IOException;
  2import java.nio.file.Files;
  3import java.nio.file.Path;
  4import java.util.ArrayList;
  5import java.util.List;
  6import java.util.Locale;
  7import java.util.Scanner;
  8
  9public class TodoCli {
 10    public static void main(String[] args) {
 11        Path saveFile = Path.of("todo-java.txt");
 12        Store store = new Store(saveFile);
 13        store.load();
 14
 15        Scanner scanner = new Scanner(System.in);
 16        System.out.println("Commands: add, list, done, remove, quit");
 17
 18        while (true) {
 19            System.out.print("> ");
 20            String command = scanner.nextLine().trim().toLowerCase(Locale.ROOT);
 21
 22            switch (command) {
 23                case "add":
 24                    System.out.print("Task: ");
 25                    String text = scanner.nextLine().trim();
 26                    if (!text.isEmpty()) {
 27                        store.add(text);
 28                        store.save();
 29                    }
 30                    break;
 31                case "list":
 32                    store.print();
 33                    break;
 34                case "done":
 35                    System.out.print("Task id: ");
 36                    int doneId = parseId(scanner.nextLine());
 37                    if (store.markDone(doneId)) {
 38                        store.save();
 39                    }
 40                    break;
 41                case "remove":
 42                    System.out.print("Task id: ");
 43                    int removeId = parseId(scanner.nextLine());
 44                    if (store.remove(removeId)) {
 45                        store.save();
 46                    }
 47                    break;
 48                case "quit":
 49                    System.out.println("Saved to " + saveFile);
 50                    return;
 51                default:
 52                    System.out.println("Use: add, list, done, remove, quit");
 53            }
 54        }
 55    }
 56
 57    private static int parseId(String raw) {
 58        try {
 59            return Integer.parseInt(raw.trim());
 60        } catch (NumberFormatException e) {
 61            return -1;
 62        }
 63    }
 64
 65    private static class Store {
 66        private final Path saveFile;
 67        private final List<Task> tasks = new ArrayList<>();
 68        private int nextId = 1;
 69
 70        private Store(Path saveFile) {
 71            this.saveFile = saveFile;
 72        }
 73
 74        private void add(String text) {
 75            tasks.add(new Task(nextId++, text, false));
 76            System.out.println("Added.");
 77        }
 78
 79        private boolean markDone(int id) {
 80            for (Task task : tasks) {
 81                if (task.id == id) {
 82                    task.done = true;
 83                    System.out.println("Marked done.");
 84                    return true;
 85                }
 86            }
 87            System.out.println("Task not found.");
 88            return false;
 89        }
 90
 91        private boolean remove(int id) {
 92            boolean removed = tasks.removeIf(task -> task.id == id);
 93            System.out.println(removed ? "Removed." : "Task not found.");
 94            return removed;
 95        }
 96
 97        private void print() {
 98            if (tasks.isEmpty()) {
 99                System.out.println("No tasks yet.");
100                return;
101            }
102            for (Task task : tasks) {
103                String box = task.done ? "[x]" : "[ ]";
104                System.out.println(task.id + ". " + box + " " + task.text);
105            }
106        }
107
108        private void load() {
109            if (!Files.exists(saveFile)) {
110                return;
111            }
112            try {
113                for (String line : Files.readAllLines(saveFile)) {
114                    String[] parts = line.split("\\|", 3);
115                    if (parts.length < 3) {
116                        continue;
117                    }
118                    int id = Integer.parseInt(parts[0]);
119                    boolean done = Boolean.parseBoolean(parts[1]);
120                    String text = parts[2];
121                    tasks.add(new Task(id, text, done));
122                    nextId = Math.max(nextId, id + 1);
123                }
124            } catch (IOException | NumberFormatException e) {
125                System.out.println("Starting with empty list.");
126                tasks.clear();
127                nextId = 1;
128            }
129        }
130
131        private void save() {
132            List<String> lines = new ArrayList<>();
133            for (Task task : tasks) {
134                lines.add(task.id + "|" + task.done + "|" + task.text);
135            }
136            try {
137                Files.write(saveFile, lines);
138            } catch (IOException e) {
139                System.out.println("Could not save tasks.");
140            }
141        }
142    }
143
144    private static class Task {
145        private final int id;
146        private final String text;
147        private boolean done;
148
149        private Task(int id, String text, boolean done) {
150            this.id = id;
151            this.text = text;
152            this.done = done;
153        }
154    }
155}

Run It

1javac TodoCli.java
2java TodoCli

Why It Matters

You get real CRUD plus persistence, but still in a small codebase you can read in one sitting.