diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/maintenance/MaintenanceResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/maintenance/MaintenanceResource.java
index 5f38329807e0..e9a368505a16 100644
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/maintenance/MaintenanceResource.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/maintenance/MaintenanceResource.java
@@ -26,6 +26,7 @@
import com.dotmarketing.exception.DoesNotExistException;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.portlets.cmsmaintenance.factories.CMSMaintenanceFactory;
+import com.dotmarketing.quartz.QuartzUtils;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.DateUtil;
import com.dotmarketing.util.FileUtil;
@@ -43,6 +44,9 @@
import io.vavr.Lazy;
import io.vavr.control.Try;
import org.apache.commons.io.IOUtils;
+import org.quartz.CronTrigger;
+import org.quartz.JobDetail;
+import org.quartz.Trigger;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -89,6 +93,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1484,6 +1489,194 @@ private static boolean containsDotCMSFrame(final StackTraceElement[] stack) {
return false;
}
+ /**
+ * Lists all Quartz scheduler jobs across every job group, with trigger details
+ * including next fire time, misfire policy, and current running status.
+ *
+ * Jobs whose detail or trigger cannot be loaded (e.g. {@code ClassNotFoundException}
+ * after an upgrade removed the job class) are still surfaced with an {@code error}
+ * field so they can be deleted via {@link #deleteSystemJob}. Jobs with no trigger
+ * are skipped, mirroring the legacy {@code system_jobs.jsp} behavior.
+ *
+ * @param request The current {@link HttpServletRequest}
+ * @param response The current {@link HttpServletResponse}
+ * @return List of Quartz job descriptors.
+ */
+ @Operation(
+ summary = "List Quartz scheduler jobs",
+ description = "Returns every Quartz scheduler job across all job groups, with "
+ + "trigger details (next fire time, misfire instruction) and current "
+ + "running status. Errored jobs (e.g. class not found after upgrade) "
+ + "are returned with an 'error' field so admins can clean them up. "
+ + "Note: these are Quartz scheduler jobs, NOT the JobQueueManager "
+ + "jobs exposed at /api/v1/jobs — those are a separate system."
+ )
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200",
+ description = "List of Quartz scheduler jobs",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(implementation = ResponseEntitySystemJobListView.class))),
+ @ApiResponse(responseCode = "401",
+ description = "Unauthorized - authentication required",
+ content = @Content(mediaType = "application/json")),
+ @ApiResponse(responseCode = "403",
+ description = "Forbidden - CMS Administrator role and Maintenance portlet access required",
+ content = @Content(mediaType = "application/json"))
+ })
+ @GET
+ @Path("/_systemJobs")
+ @NoCache
+ @Produces({MediaType.APPLICATION_JSON})
+ public final ResponseEntitySystemJobListView listSystemJobs(
+ @Parameter(hidden = true) @Context final HttpServletRequest request,
+ @Parameter(hidden = true) @Context final HttpServletResponse response) {
+
+ assertBackendUser(request, response);
+
+ final List