diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
index a135d73eee8b706e8a221b34fef41c4024422006..dbaa9a47cb670dca99a0bddd5f33b528cbd24abe 100644
--- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
+++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
@@ -13,6 +13,10 @@ import java.util.Set;
 
 /**
  * The node renderer that renders all the core nodes (comes last in the order of node renderers).
+ * <p>
+ * Note that while sometimes it would be easier to record what kind of syntax was used on parsing (e.g. ATX vs Setext
+ * heading), this renderer is intended to also work for documents that were created by directly creating
+ * {@link Node Nodes} instead. So in order to support that, it sometimes needs to do a bit more work.
  */
 public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer {
 
@@ -211,11 +215,34 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
 
     @Override
     public void visit(Heading heading) {
+        if (heading.getLevel() <= 2) {
+            LineBreakVisitor lineBreakVisitor = new LineBreakVisitor();
+            heading.accept(lineBreakVisitor);
+            boolean isMultipleLines = lineBreakVisitor.hasLineBreak();
+
+            if (isMultipleLines) {
+                // Setext headings: Can have multiple lines, but only level 1 or 2
+                visitChildren(heading);
+                writer.line();
+                if (heading.getLevel() == 1) {
+                    // Note that it would be nice to match the length of the contents instead of just using 3, but that's
+                    // not easy.
+                    writer.write("===");
+                } else {
+                    writer.write("---");
+                }
+                writer.block();
+                return;
+            }
+        }
+
+        // ATX headings: Can't have multiple lines, but up to level 6.
         for (int i = 0; i < heading.getLevel(); i++) {
             writer.write('#');
         }
         writer.write(' ');
         visitChildren(heading);
+
         writer.block();
     }
 
@@ -392,4 +419,24 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
             number = orderedList.getStartNumber();
         }
     }
+
+    private static class LineBreakVisitor extends AbstractVisitor {
+        private boolean lineBreak = false;
+
+        public boolean hasLineBreak() {
+            return lineBreak;
+        }
+
+        @Override
+        public void visit(SoftLineBreak softLineBreak) {
+            super.visit(softLineBreak);
+            lineBreak = true;
+        }
+
+        @Override
+        public void visit(HardLineBreak hardLineBreak) {
+            super.visit(hardLineBreak);
+            lineBreak = true;
+        }
+    }
 }
diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java
index 4dc8dbff9cb5918506403f027587cf1c03f734d8..4dee17ed622173f8be7a2933d852c79c03c451c2 100644
--- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java
+++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java
@@ -14,7 +14,7 @@ import java.util.List;
  * <p>
  * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any):
  * <ul>
- *     <li>Headings are always output as ATX headings for simplicity</li>
+ *     <li>Headings are output as ATX headings if possible (multi-line headings need Setext headings)</li>
  *     <li>Escaping might be over-eager, e.g. a plain {@code *} might be escaped
  *     even though it doesn't need to be in that particular context</li>
  * </ul>
diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java
index f6ce1b4e05f0eb38ab2c66b1d6fa37f425c5370c..05a253fde7863144e92f9cc306f3089e81acbe9e 100644
--- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java
+++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java
@@ -28,6 +28,9 @@ public class MarkdownRendererTest {
         assertRoundTrip("##### foo\n");
         assertRoundTrip("###### foo\n");
 
+        assertRoundTrip("Foo\nbar\n===\n");
+        assertRoundTrip("[foo\nbar](/url)\n===\n");
+
         assertRoundTrip("# foo\n\nbar\n");
     }
 
diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java
index 4e9396e777f5b3dc147171326483a3bb7b702836..1dd06441467d84cd374966cd2356b6e8897722e3 100644
--- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java
+++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java
@@ -62,7 +62,7 @@ public class SpecMarkdownRendererTest {
             System.out.println();
         }
 
-        int expectedPassed = 622;
+        int expectedPassed = 625;
         assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed);
     }