diff --git a/2021/README.md b/2021/README.md
index 4d512b013779fecc8dc33283ddcba8235c251e54..49a7860fa05b7ed1ef3065cf4fa339171a6d9a77 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -42,4 +42,16 @@ intractable.
 For example, in the problem statement practically *screams* that the
 obvious solution grows exponentially. Normally, death would keep the population
 under control, but I guess Death Takes a Holiday. One little extra gotcha:
-26984457539 > Integer.MAX_INT, so I need to use a long integer.
\ No newline at end of file
+26984457539 > Integer.MAX_INT, so I need to use a long integer.
+
+## Day 7
+
+Naively trying each position is reasonable: O(num_crabs * max_distance). Or,
+we could follow a similar model of Day 6, in which we track the number of crabs
+in each position, which would be O(max_distance^2). These are both very
+manageable, so I'll go with tracking each crab since that requires less upfront
+work.
+
+Part 2 looks a bit more interesting.  We *could* go for an
+O(num_crabs * max_distance^2) solution, or we could recognize what Euler
+recognized: ∑_{i=1}^{n}i = n(n+1)/2.
diff --git a/2021/src/main/java/edu/unl/cse/bohn/year2021/Day7.java b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day7.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d5f9ee3d5bbea40bf425afc0d00b903a9360e7b
--- /dev/null
+++ b/2021/src/main/java/edu/unl/cse/bohn/year2021/Day7.java
@@ -0,0 +1,48 @@
+package edu.unl.cse.bohn.year2021;
+
+import edu.unl.cse.bohn.Puzzle;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class Day7 extends Puzzle {
+    long[] crabs;
+    long maximum_position;
+
+    public Day7(boolean isProductionReady) {
+        super(isProductionReady);
+        sampleData = "16,1,2,0,4,2,7,1,2,14";
+    }
+
+    @Override
+    public long computePart1(List<String> data) {
+        crabs = Arrays.stream(data.get(0).split(",")).mapToLong(Long::parseLong).toArray();
+        maximum_position = Arrays.stream(crabs).max().orElseThrow();
+        long minimumCost = Long.MAX_VALUE;
+        for (long position = 0; position <= maximum_position; position++) {
+            long fuelCost = 0;
+            for (long crab : crabs) {
+                fuelCost += Math.abs(crab - position);
+            }
+            minimumCost = Long.min(minimumCost, fuelCost);
+        }
+        return minimumCost;
+    }
+
+    @Override
+    public long computePart2(List<String> data) {
+        crabs = Arrays.stream(data.get(0).split(",")).mapToLong(Long::parseLong).toArray();
+        maximum_position = Arrays.stream(crabs).max().orElseThrow();
+        long minimumCost = Long.MAX_VALUE;
+        for (long position = 0; position <= maximum_position; position++) {
+            long fuelCost = 0;
+            for (long crab : crabs) {
+                long displacement = Math.abs(crab - position);
+                fuelCost += displacement * (displacement + 1) / 2;
+            }
+            minimumCost = Long.min(minimumCost, fuelCost);
+        }
+        return minimumCost;
+    }
+}