diff --git a/src/main/java/com/thealgorithms/graph/Bridges.java b/src/main/java/com/thealgorithms/graph/Bridges.java
new file mode 100644
index 000000000000..6f64403691db
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/Bridges.java
@@ -0,0 +1,112 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Finds bridges in an undirected graph using depth-first search.
+ *
+ *
A bridge is an edge whose removal increases the number of connected
+ * components in the graph.
+ *
+ *
Vertices are numbered from 0 to {@code vertexCount - 1}.
+ *
+ *
Time complexity: O(V + E), where V is the number of vertices
+ * and E is the number of edges.
+ *
+ *
Reference: https://en.wikipedia.org/wiki/Bridge_(graph_theory)
+ */
+public final class Bridges {
+
+ private Bridges() {
+ // Utility class; do not instantiate.
+ }
+
+ /**
+ * Returns all bridges in an undirected graph.
+ *
+ * @param vertexCount number of vertices
+ * @param edges array of edges, with each edge represented as {u, v}
+ * @return list of bridges, with each bridge represented as {u, v}
+ * @throws IllegalArgumentException if the vertex count is negative,
+ * or an edge is null, malformed,
+ * or references an invalid vertex
+ */
+ public static List findBridges(int vertexCount, int[][] edges) {
+ if (vertexCount < 0) {
+ throw new IllegalArgumentException("Vertex count cannot be negative.");
+ }
+
+ if (edges == null) {
+ throw new IllegalArgumentException("Edges cannot be null.");
+ }
+
+ List> adjacencyList = new ArrayList<>(vertexCount);
+ for (int i = 0; i < vertexCount; i++) {
+ adjacencyList.add(new ArrayList<>());
+ }
+
+ for (int edgeId = 0; edgeId < edges.length; edgeId++) {
+ int[] edge = edges[edgeId];
+ if (edge == null || edge.length != 2) {
+ throw new IllegalArgumentException("Each edge must contain exactly two vertices.");
+ }
+
+ int u = edge[0];
+ int v = edge[1];
+ if (u < 0 || u >= vertexCount || v < 0 || v >= vertexCount) {
+ throw new IllegalArgumentException("Edge contains an invalid vertex.");
+ }
+
+ adjacencyList.get(u).add(new int[] {v, edgeId});
+ adjacencyList.get(v).add(new int[] {u, edgeId});
+ }
+
+ int[] discoveryTime = new int[vertexCount];
+ int[] low = new int[vertexCount];
+ Arrays.fill(discoveryTime, -1);
+
+ List bridges = new ArrayList<>();
+ int[] timer = {0};
+
+ for (int vertex = 0; vertex < vertexCount; vertex++) {
+ if (discoveryTime[vertex] == -1) {
+ dfs(vertex, -1, adjacencyList, discoveryTime, low, timer, bridges);
+ }
+ }
+
+ bridges.sort((first, second) -> {
+ int comparison = Integer.compare(first[0], second[0]);
+ return comparison != 0 ? comparison : Integer.compare(first[1], second[1]);
+ });
+
+ return bridges;
+ }
+
+ private static void dfs(int vertex, int parentEdgeId, List> adjacencyList, int[] discoveryTime, int[] low, int[] timer, List bridges) {
+ discoveryTime[vertex] = timer[0];
+ low[vertex] = timer[0]++;
+
+ for (int[] adjacent : adjacencyList.get(vertex)) {
+ int neighbor = adjacent[0];
+ int edgeId = adjacent[1];
+
+ if (edgeId == parentEdgeId) {
+ continue;
+ }
+
+ if (discoveryTime[neighbor] != -1) {
+ low[vertex] = Math.min(low[vertex], discoveryTime[neighbor]);
+ } else {
+ dfs(neighbor, edgeId, adjacencyList, discoveryTime, low, timer, bridges);
+
+ low[vertex] = Math.min(low[vertex], low[neighbor]);
+
+ if (low[neighbor] > discoveryTime[vertex]) {
+ bridges.add(new int[] {Math.min(vertex, neighbor), Math.max(vertex, neighbor)});
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/BridgesTest.java b/src/test/java/com/thealgorithms/graph/BridgesTest.java
new file mode 100644
index 000000000000..7a3774c9aee2
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/BridgesTest.java
@@ -0,0 +1,166 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class BridgesTest {
+
+ @Test
+ void testTriangleHasNoBridges() {
+ int[][] edges = {{0, 1}, {1, 2}, {2, 0}};
+
+ List result = Bridges.findBridges(3, edges);
+
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ void testChainHasTwoBridges() {
+ int[][] edges = {{0, 1}, {1, 2}};
+
+ List result = Bridges.findBridges(3, edges);
+
+ assertEquals(2, result.size());
+ assertArrayEquals(new int[] {0, 1}, result.get(0));
+ assertArrayEquals(new int[] {1, 2}, result.get(1));
+ }
+
+ @Test
+ void testDisconnectedGraph() {
+ int[][] edges = {{0, 1}, {2, 3}, {3, 4}};
+
+ List result = Bridges.findBridges(5, edges);
+
+ assertEquals(3, result.size());
+ assertArrayEquals(new int[] {0, 1}, result.get(0));
+ assertArrayEquals(new int[] {2, 3}, result.get(1));
+ assertArrayEquals(new int[] {3, 4}, result.get(2));
+ }
+
+ @Test
+ void testEmptyGraph() {
+ List result = Bridges.findBridges(4, new int[0][]);
+
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ void testZeroVertices() {
+ List result = Bridges.findBridges(0, new int[0][]);
+
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ void testParallelEdgesAreNotBridges() {
+ int[][] edges = {{0, 1}, {0, 1}};
+
+ List result = Bridges.findBridges(2, edges);
+
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ void testSelfLoopIsNotBridge() {
+ int[][] edges = {{0, 0}};
+
+ List result = Bridges.findBridges(1, edges);
+
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ void testSingleEdgeIsBridge() {
+ int[][] edges = {{0, 1}};
+
+ List result = Bridges.findBridges(2, edges);
+
+ assertEquals(1, result.size());
+ assertArrayEquals(new int[] {0, 1}, result.get(0));
+ }
+
+ @Test
+ void testCycleWithTail() {
+ int[][] edges = {{0, 1}, {1, 2}, {2, 0}, {2, 3}};
+
+ List result = Bridges.findBridges(4, edges);
+
+ assertEquals(1, result.size());
+ assertArrayEquals(new int[] {2, 3}, result.get(0));
+ }
+
+ @Test
+ void testNegativeVertexCountThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(-1, new int[0][]));
+ }
+
+ @Test
+ void testNullEdgesThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, null));
+ }
+
+ @Test
+ void testMalformedEdgeThrowsException() {
+ int[][] edges = {{0}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testEdgeWithTooManyVerticesThrowsException() {
+ int[][] edges = {{0, 1, 2}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(3, edges));
+ }
+
+ @Test
+ void testNullEdgeThrowsException() {
+ int[][] edges = {null};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testNegativeFirstVertexThrowsException() {
+ int[][] edges = {{-1, 1}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testNegativeSecondVertexThrowsException() {
+ int[][] edges = {{0, -1}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testFirstVertexOutOfRangeThrowsException() {
+ int[][] edges = {{2, 1}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testSecondVertexOutOfRangeThrowsException() {
+ int[][] edges = {{0, 2}};
+
+ assertThrows(IllegalArgumentException.class, () -> Bridges.findBridges(2, edges));
+ }
+
+ @Test
+ void testBridgesAreSortedByBothEndpoints() {
+ int[][] edges = {{2, 3}, {0, 2}, {0, 1}};
+
+ List result = Bridges.findBridges(4, edges);
+
+ assertEquals(3, result.size());
+ assertArrayEquals(new int[] {0, 1}, result.get(0));
+ assertArrayEquals(new int[] {0, 2}, result.get(1));
+ assertArrayEquals(new int[] {2, 3}, result.get(2));
+ }
+}