diff --git a/2022/README.md b/2022/README.md
index a90e517209f6960b8fd6903cdebc1d6b7fef2f94..9d7ef625411f9165b1be2b36985cd050aa573ed4 100644
--- a/2022/README.md
+++ b/2022/README.md
@@ -477,5 +477,42 @@ Monkey 3 inspected items 103 times.
 
 ## Day 12
 
+First, a closing thought on yesterday's problem.
+The concept I stumbled upon is the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
+which apparently has an [Advent](https://twitter.com/Lincoln81/status/1446897782351089675)
+[of](https://twitter.com/fixedpointfae/status/1601842705541517313)
+[Code](https://twitter.com/PeopleNArthax/status/1601936116055408640)
+[reputation](https://twitter.com/DarkCisum/status/1338382928129224705).
+
+And now on to today's adventure.
+
+- [The problem](https://adventofcode.com/2022/day/12)
+- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day12.java)
+
+### Part 1
+
+Speaking of AOC staples, it looks like today we need to implement a shortest path algorithm.
+
+The subproblems are
+
+- Create locations and build a reachability graph (unweighted, directional)
+    - Compare heights
+- Find the shortest path from the start to the finish
+
+My critique of my part 1 solution
+
+- I'm not thrilled that I'm storing the distance from the start as a `Location` attribute; I suppose the alternative is
+  to create a `Map<Location,Integer>`
+
+### Part 2
+
+The difference is that we're now examining many possible starting locations.
+A naïve approach would be to run the algorithm I developed for part 1, which starts at `start`, using each 'a'
+`Location` as a starting point, in turn.
+Alternatively, we could work backwards from `finish` and find the distance of each `Location` to the `finish`.
+Then look for the shortest distance to the finish from the 'a' `Location`s.
+
+## Day 13
+
 (coming soon)
 
diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2761b5070cf4adfe3cbb0c35a81ccd6b120ceb0
--- /dev/null
+++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day12.java
@@ -0,0 +1,150 @@
+package edu.unl.cse.bohn.year2022;
+
+import edu.unl.cse.bohn.Puzzle;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("unused")
+public class Day12 extends Puzzle {
+    public Day12(boolean isProductionReady) {
+        super(isProductionReady);
+        //noinspection SpellCheckingInspection
+        sampleData = """
+                Sabqponm
+                abcryxxl
+                accszExk
+                acctuvwj
+                abdefghi""";
+    }
+
+    private Location start = null, finish = null;
+    private Set<Location> locations;
+
+    @Override
+    public long computePart1(List<String> data) {
+        buildReachabilityGraph(data);
+        computeDistancesToFinish(false);
+        return start.getDistanceToFinish();
+    }
+
+    @Override
+    public long computePart2(List<String> data) {
+        if (start == null) {
+            this.computePart1(data);
+        }
+        return locations.stream()
+                .filter(location -> location.getElevation() == 'a')
+                .map(Location::getDistanceToFinish)
+                .min(Integer::compareTo).orElseThrow();
+    }
+
+    private void computeDistancesToFinish(@SuppressWarnings("SameParameterValue") boolean verbose) {
+        int numberOfLocations = locations.size();
+        PriorityQueue<Location> locationsToVisit = new PriorityQueue<>();
+        finish.updateDistanceFromFinish(0);
+        locationsToVisit.add(finish);
+        Location currentLocation;
+        while (!locationsToVisit.isEmpty()) {
+            currentLocation = locationsToVisit.remove();
+            for (Location reachableLocation : currentLocation.getReachableLocations()) {
+                if (reachableLocation.updateDistanceFromFinish(currentLocation.getDistanceToFinish() + 1)) {
+                    locationsToVisit.remove(reachableLocation);     // just in case it's already there
+                    locationsToVisit.add(reachableLocation);
+                }
+                if (verbose) {
+                    System.out.println("Number of locations to visit = " + locationsToVisit.size()
+                            + " (out of " + numberOfLocations + ")");
+                }
+                if (locationsToVisit.size() > numberOfLocations) {
+                    throw new IllegalStateException("The queue contains more locations (" + locationsToVisit.size()
+                            + ") than exist (" + numberOfLocations + ").");
+                }
+            }
+        }
+    }
+
+    private void buildReachabilityGraph(List<String> data) {
+        // create the locations
+        Location[][] locations = new Location[data.size()][data.get(0).length()];
+        for (int i = 0; i < data.size(); i++) {
+            for (int j = 0; j < data.get(i).length(); j++) {
+                char elevation = data.get(i).charAt(j);
+                switch (elevation) {
+                    case 'S' -> {
+                        locations[i][j] = new Location('a');
+                        start = locations[i][j];
+                    }
+                    case 'E' -> {
+                        locations[i][j] = new Location('z');
+                        finish = locations[i][j];
+                    }
+                    default -> locations[i][j] = new Location(elevation);
+                }
+            }
+        }
+        // build the graph
+        for (int i = 0; i < locations.length; i++) {
+            for (int j = 0; j < locations[i].length; j++) {
+                if (i > 0) {
+                    locations[i][j].setLocationAsReachableIfNotTooTall(locations[i - 1][j]);
+                }
+                if (i < locations.length - 1) {
+                    locations[i][j].setLocationAsReachableIfNotTooTall(locations[i + 1][j]);
+                }
+                if (j > 0) {
+                    locations[i][j].setLocationAsReachableIfNotTooTall(locations[i][j - 1]);
+                }
+                if (j < locations[i].length - 1) {
+                    locations[i][j].setLocationAsReachableIfNotTooTall(locations[i][j + 1]);
+                }
+            }
+        }
+        this.locations = Arrays.stream(locations).flatMap(Arrays::stream).collect(Collectors.toSet());
+    }
+
+    private static class Location implements Comparable<Location> {
+        private final Set<Location> reachableLocations;
+        private final char elevation;
+        private int distanceToFinish;
+
+        public Location(char elevation) {
+            this.elevation = elevation;
+            this.reachableLocations = new HashSet<>();
+            this.distanceToFinish = Integer.MAX_VALUE;
+        }
+
+        public void setLocationAsReachableIfNotTooTall(Location otherLocation) {
+            if (this.elevation <= otherLocation.elevation + 1) {
+                reachableLocations.add(otherLocation);
+            }
+        }
+
+        public boolean updateDistanceFromFinish(int possiblyShorterDistance) {
+            boolean newDistanceIsShorter = possiblyShorterDistance < distanceToFinish;
+            distanceToFinish = newDistanceIsShorter ? possiblyShorterDistance : distanceToFinish;
+            return newDistanceIsShorter;
+        }
+
+        public int getDistanceToFinish() {
+            return distanceToFinish;
+        }
+
+        public boolean canReach(Location destination) {
+            return reachableLocations.contains(destination);
+        }
+
+        public Set<Location> getReachableLocations() {
+            return Collections.unmodifiableSet(reachableLocations);
+        }
+
+        public char getElevation() {
+            return elevation;
+        }
+
+        @Override
+        public int compareTo(Location other) {
+            return this.elevation - other.elevation;
+        }
+    }
+}