diff --git a/README.md b/README.md
index 7a7fdf97..809323d2 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
 | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
 | python3 | [✓](src/main/python/AoC2024_01.py) | [✓](src/main/python/AoC2024_02.py) | [✓](src/main/python/AoC2024_03.py) | [✓](src/main/python/AoC2024_04.py) | [✓](src/main/python/AoC2024_05.py) | [✓](src/main/python/AoC2024_06.py) |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
-| java | [✓](src/main/java/AoC2024_01.java) | [✓](src/main/java/AoC2024_02.java) | [✓](src/main/java/AoC2024_03.java) | [✓](src/main/java/AoC2024_04.java) | [✓](src/main/java/AoC2024_05.java) |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
+| java | [✓](src/main/java/AoC2024_01.java) | [✓](src/main/java/AoC2024_02.java) | [✓](src/main/java/AoC2024_03.java) | [✓](src/main/java/AoC2024_04.java) | [✓](src/main/java/AoC2024_05.java) | [✓](src/main/java/AoC2024_06.java) |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
 | rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | [✓](src/main/rust/AoC2024_04/src/main.rs) | [✓](src/main/rust/AoC2024_05/src/main.rs) |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
 <!-- @END:ImplementationsTable:2024@ -->
 
diff --git a/src/main/java/AoC2024_06.java b/src/main/java/AoC2024_06.java
new file mode 100644
index 00000000..f63833b8
--- /dev/null
+++ b/src/main/java/AoC2024_06.java
@@ -0,0 +1,120 @@
+import static java.util.stream.Collectors.toSet;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.github.pareronia.aoc.CharGrid;
+import com.github.pareronia.aoc.Grid.Cell;
+import com.github.pareronia.aoc.geometry.Direction;
+import com.github.pareronia.aoc.geometry.Turn;
+import com.github.pareronia.aoc.solution.Sample;
+import com.github.pareronia.aoc.solution.Samples;
+import com.github.pareronia.aoc.solution.SolutionBase;
+
+public final class AoC2024_06
+        extends SolutionBase<AoC2024_06.Input, Integer, Integer> {
+    
+    private AoC2024_06(final boolean debug) {
+        super(debug);
+    }
+    
+    public static AoC2024_06 create() {
+        return new AoC2024_06(false);
+    }
+    
+    public static AoC2024_06 createDebug() {
+        return new AoC2024_06(true);
+    }
+    
+    @Override
+    protected Input parseInput(final List<String> inputs) {
+        final CharGrid grid = new CharGrid(inputs);
+        final Cell start = grid.getAllEqualTo('^').findFirst().orElseThrow();
+        final Set<Cell> obs = grid.getAllEqualTo('#').collect(toSet());
+        return new Input(grid, obs, start);
+    }
+    
+    private Route route(
+        final CharGrid grid, final Set<Cell> obs, Cell pos, Direction dir
+    ) {
+        final LinkedHashMap<Cell, List<Direction>> seen = new LinkedHashMap<>();
+        seen.computeIfAbsent(pos, k -> new ArrayList<>()).add(dir);
+        while (true) {
+            final Cell nxt = pos.at(dir);
+            if (!grid.isInBounds(nxt)) {
+                return new Route(false, seen);
+            }
+            if (obs.contains(nxt)) {
+                dir = dir.turn(Turn.RIGHT);
+            } else {
+                pos = nxt;
+            }
+            if (seen.containsKey(pos) && seen.get(pos).contains(dir)) {
+                return new Route(true, null);
+            }
+            seen.computeIfAbsent(pos, k -> new ArrayList<>()).add(dir);
+        }
+    }
+    
+    @Override
+    public Integer solvePart1(final Input input) {
+        final Route route
+                = route(input.grid, input.obs, input.start, Direction.UP);
+        return route.path.size();
+    }
+    
+    @Override
+    public Integer solvePart2(final Input input) {
+        Route route = route(input.grid, input.obs, input.start, Direction.UP);
+        final Iterator<Entry<Cell, List<Direction>>> it
+                = route.path.entrySet().iterator();
+        Entry<Cell, List<Direction>> prev = it.next();
+        int ans = 0;
+        while (it.hasNext()) {
+            final Entry<Cell, List<Direction>> curr = it.next();
+            input.obs.add(curr.getKey());
+            final Cell start = prev.getKey();
+            final Direction dir = prev.getValue().remove(0);
+            route = route(input.grid, input.obs, start, dir);
+            if (route.loop) {
+                ans += 1;
+            }
+            input.obs.remove(curr.getKey());
+            prev = curr;
+        }
+        return ans;
+    }
+    
+    @Override
+    @Samples({
+        @Sample(method = "part1", input = TEST, expected = "41"),
+        @Sample(method = "part2", input = TEST, expected = "6"),
+    })
+    public void samples() {
+    }
+    
+    public static void main(final String[] args) throws Exception {
+        AoC2024_06.create().run();
+    }
+
+    private static final String TEST = """
+            ....#.....
+            .........#
+            ..........
+            ..#.......
+            .......#..
+            ..........
+            .#..^.....
+            ........#.
+            #.........
+            ......#...
+            """;
+
+    record Input(CharGrid grid, Set<Cell> obs, Cell start) {}
+
+    record Route(boolean loop, LinkedHashMap<Cell, List<Direction>> path) {}
+}
\ No newline at end of file