diff --git a/jgrapht-core/pom.xml b/jgrapht-core/pom.xml
index cca88c02c29..97c454a8731 100644
--- a/jgrapht-core/pom.xml
+++ b/jgrapht-core/pom.xml
@@ -88,6 +88,8 @@
Ref: https://github.com/jgrapht/jgrapht/issues/1170
-->
--add-exports org.jgrapht.core/org.jgrapht.perf.clique=ALL-UNNAMED
+ --add-exports org.jgrapht.core/org.jgrapht.perf.clustering=ALL-UNNAMED
+ --add-exports org.jgrapht.core/org.jgrapht.perf.clustering.jmh_generated=ALL-UNNAMED
--add-exports org.jgrapht.core/org.jgrapht.perf.connectivity=ALL-UNNAMED
--add-exports org.jgrapht.core/org.jgrapht.perf.flow=ALL-UNNAMED
--add-exports org.jgrapht.core/org.jgrapht.perf.graph=ALL-UNNAMED
diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LouvainClustering.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LouvainClustering.java
new file mode 100644
index 00000000000..5ac400286e7
--- /dev/null
+++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LouvainClustering.java
@@ -0,0 +1,377 @@
+/*
+ * (C) Copyright 2026-2026, by seilat and Contributors.
+ *
+ * JGraphT : a free Java graph-theory library
+ *
+ * See the CONTRIBUTORS.md file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the
+ * GNU Lesser General Public License v2.1 or later
+ * which is available at
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
+ */
+package org.jgrapht.alg.clustering;
+
+import org.jgrapht.*;
+import org.jgrapht.alg.interfaces.*;
+
+import java.util.*;
+
+/**
+ * The Louvain method for community detection.
+ *
+ *
+ * Greedily optimizes the
+ * modularity of a vertex
+ * partition. The algorithm is described in detail in the following
+ * paper:
+ *
+ * - Blondel, V. D., Guillaume, J. L., Lambiotte, R., and Lefebvre, E. (2008). Fast unfolding of
+ * communities in large networks. Journal of Statistical Mechanics: Theory and Experiment, 2008(10),
+ * P10008.
+ *
+ *
+ *
+ * The method proceeds in alternating phases. In the local-moving phase every vertex starts
+ * in its own community and is repeatedly moved to the neighbouring community that yields the
+ * largest positive gain in modularity, until no move improves the objective. In the
+ * aggregation phase the discovered communities are contracted into super-vertices (with
+ * intra-community edges becoming self-loops) and the two phases repeat on the smaller graph. The
+ * process stops once a local-moving phase merges no communities. The final partition is projected
+ * back onto the original vertices.
+ *
+ *
+ * The algorithm runs on undirected graphs and supports edge weights; parallel edges are collapsed
+ * by summing their weights and self-loops are honoured using the same conventions as
+ * {@link UndirectedModularityMeasurer}. Edge weights must be non-negative (modularity is undefined
+ * for negative weights); a negative weight triggers an {@link IllegalArgumentException} when the
+ * clustering is computed. Its empirical running time is close to linear in the number of edges,
+ * although no worst-case guarantee is provided.
+ *
+ *
+ * The local-moving phase visits vertices in a random order, so two runs on the same graph may
+ * return different (but typically similar quality) partitions. Supply a seeded {@link Random} via
+ * the constructor for deterministic behaviour.
+ *
+ * @author seilat
+ *
+ * @param the graph vertex type
+ * @param the graph edge type
+ */
+public class LouvainClustering implements ClusteringAlgorithm
+{
+ /**
+ * Default minimum modularity gain that justifies moving a vertex to another community. Guards
+ * against floating-point oscillation in the local-moving phase.
+ */
+ public static final double DEFAULT_TOLERANCE = 1e-7;
+
+ private final Graph graph;
+ private final Random rng;
+ private final double tolerance;
+
+ private Clustering result;
+ private double modularity;
+
+ /**
+ * Create a new clustering algorithm with a fresh random number generator.
+ *
+ * @param graph the graph (needs to be undirected)
+ */
+ public LouvainClustering(Graph graph)
+ {
+ this(graph, new Random(), DEFAULT_TOLERANCE);
+ }
+
+ /**
+ * Create a new clustering algorithm with a user-supplied random number generator. Provide a
+ * seeded generator for reproducible results.
+ *
+ * @param graph the graph (needs to be undirected)
+ * @param rng random number generator
+ */
+ public LouvainClustering(Graph graph, Random rng)
+ {
+ this(graph, rng, DEFAULT_TOLERANCE);
+ }
+
+ /**
+ * Create a new clustering algorithm.
+ *
+ * @param graph the graph (needs to be undirected)
+ * @param rng random number generator
+ * @param tolerance minimum modularity gain that justifies moving a vertex; must be non-negative
+ */
+ public LouvainClustering(Graph graph, Random rng, double tolerance)
+ {
+ this.graph = GraphTests.requireUndirected(graph);
+ this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null");
+ if (tolerance < 0d) {
+ throw new IllegalArgumentException("Tolerance cannot be negative");
+ }
+ this.tolerance = tolerance;
+ }
+
+ @Override
+ public Clustering getClustering()
+ {
+ if (result == null) {
+ compute();
+ }
+ return result;
+ }
+
+ /**
+ * Returns the modularity of the computed clustering. The clustering is computed on first
+ * access. The modularity of a graph with no positive total edge weight (no edges, or all edge
+ * weights {@code 0}) is defined here to be {@code 0}.
+ *
+ * @return the modularity of the clustering in the range $[-0.5, 1)$
+ */
+ public double getModularity()
+ {
+ getClustering();
+ return modularity;
+ }
+
+ private void compute()
+ {
+ List indexToVertex = new ArrayList<>(graph.vertexSet());
+ final int n = indexToVertex.size();
+ if (n == 0) {
+ result = new ClusteringImpl<>(Collections.emptyList());
+ modularity = 0d;
+ return;
+ }
+ Map vertexToIndex = new HashMap<>(n);
+ for (int i = 0; i < n; i++) {
+ vertexToIndex.put(indexToVertex.get(i), i);
+ }
+
+ // Build the level-0 weighted adjacency, collapsing parallel edges and tracking self-loops.
+ List