Pomodoro Timer TUI in Java

This is a complete, runnable Pomodoro timer in one Java file.

Read the mental model first

Download source file

Quick Run (from repo root)

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

It supports:

  • configurable focus and break lengths
  • start, pause, resume, and reset
  • completed session counter
  • terminal bell on phase transitions

Code (Single Public Class)

  1import java.util.Locale;
  2import java.util.Scanner;
  3
  4public class PomodoroTimer {
  5    public static void main(String[] args) {
  6        Scanner scanner = new Scanner(System.in);
  7
  8        System.out.print("Focus minutes (default 25): ");
  9        int focusMinutes = parsePositiveInt(scanner.nextLine(), 25);
 10        System.out.print("Break minutes (default 5): ");
 11        int breakMinutes = parsePositiveInt(scanner.nextLine(), 5);
 12
 13        TimerState state = new TimerState(
 14                focusMinutes * 60,
 15                breakMinutes * 60
 16        );
 17
 18        System.out.println(
 19                "Commands: start, pause, resume, reset, status, quit"
 20        );
 21
 22        while (true) {
 23            if (state.running) {
 24                sleepMs(1000);
 25                state.tick();
 26                System.out.println(state.statusLine());
 27            } else {
 28                System.out.print("> ");
 29                String command = scanner.nextLine().trim().toLowerCase(
 30                        Locale.ROOT
 31                );
 32                if (handleCommand(command, state)) {
 33                    break;
 34                }
 35            }
 36        }
 37    }
 38
 39    private static boolean handleCommand(String command, TimerState state) {
 40        switch (command) {
 41            case "start":
 42            case "resume":
 43                state.running = true;
 44                System.out.println("Timer running.");
 45                return false;
 46            case "pause":
 47                state.running = false;
 48                System.out.println("Timer paused.");
 49                return false;
 50            case "reset":
 51                state.resetCurrentPhase();
 52                state.running = false;
 53                System.out.println("Current phase reset.");
 54                return false;
 55            case "status":
 56                System.out.println(state.statusLine());
 57                return false;
 58            case "quit":
 59                System.out.println("Good work today.");
 60                return true;
 61            default:
 62                System.out.println(
 63                        "Unknown command. Use start, pause, resume, " +
 64                        "reset, status, quit"
 65                );
 66                return false;
 67        }
 68    }
 69
 70    private static int parsePositiveInt(String value, int fallback) {
 71        try {
 72            int parsed = Integer.parseInt(value.trim());
 73            return parsed > 0 ? parsed : fallback;
 74        } catch (NumberFormatException e) {
 75            return fallback;
 76        }
 77    }
 78
 79    private static void sleepMs(long ms) {
 80        try {
 81            Thread.sleep(ms);
 82        } catch (InterruptedException e) {
 83            Thread.currentThread().interrupt();
 84        }
 85    }
 86
 87    private static class TimerState {
 88        private final int focusSeconds;
 89        private final int breakSeconds;
 90        private int remainingSeconds;
 91        private boolean focusPhase = true;
 92        private int completedFocusSessions = 0;
 93        private boolean running = false;
 94
 95        private TimerState(int focusSeconds, int breakSeconds) {
 96            this.focusSeconds = focusSeconds;
 97            this.breakSeconds = breakSeconds;
 98            this.remainingSeconds = focusSeconds;
 99        }
100
101        private void tick() {
102            remainingSeconds--;
103            if (remainingSeconds > 0) {
104                return;
105            }
106
107            if (focusPhase) {
108                completedFocusSessions++;
109            }
110
111            focusPhase = !focusPhase;
112            remainingSeconds = focusPhase ? focusSeconds : breakSeconds;
113            System.out.print("\u0007");
114            System.out.println(
115                    focusPhase
116                            ? "Break complete. Focus time."
117                            : "Focus complete. Break time."
118            );
119        }
120
121        private String statusLine() {
122            int safeSeconds = Math.max(0, remainingSeconds);
123            int minutes = safeSeconds / 60;
124            int seconds = safeSeconds % 60;
125            String phase = focusPhase ? "FOCUS" : "BREAK";
126            String state = running ? "RUNNING" : "PAUSED";
127            return String.format(
128                    "%s %02d:%02d | sessions=%d | %s",
129                    phase,
130                    minutes,
131                    seconds,
132                    completedFocusSessions,
133                    state
134            );
135        }
136
137        private void resetCurrentPhase() {
138            remainingSeconds = focusPhase ? focusSeconds : breakSeconds;
139        }
140    }
141}

Run It

1javac PomodoroTimer.java
2java PomodoroTimer

Why this works for beginners

  • One public class keeps the file approachable.
  • Nested TimerState separates timing logic from command input.
  • The loop is predictable: read command or tick one second.