diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml
index a71ec98e4..cd2bf074f 100644
--- a/spring-cloud-alibaba-dependencies/pom.xml
+++ b/spring-cloud-alibaba-dependencies/pom.xml
@@ -30,6 +30,10 @@
         <spring.ai.version>0.8.1</spring.ai.version>
         <dashscope-sdk-java.version>2.14.0</dashscope-sdk-java.version>
 
+        <!-- scheduling -->
+        <shedlock.version>4.23.0</shedlock.version>
+        <schedulerx.worker.version>1.11.4</schedulerx.worker.version>
+
         <!-- Maven Plugin Versions -->
         <maven-source-plugin.version>3.2.1</maven-source-plugin.version>
         <maven-javadoc-plugin.version>3.1.1</maven-javadoc-plugin.version>
@@ -185,6 +189,23 @@
                 <version>${rocketmq.version}</version>
             </dependency>
 
+            <!-- Alibaba scheduling -->
+            <dependency>
+                <groupId>net.javacrumbs.shedlock</groupId>
+                <artifactId>shedlock-spring</artifactId>
+                <version>${shedlock.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>net.javacrumbs.shedlock</groupId>
+                <artifactId>shedlock-provider-jdbc-template</artifactId>
+                <version>${shedlock.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.aliyun.schedulerx</groupId>
+                <artifactId>schedulerx2-worker</artifactId>
+                <version>${schedulerx.worker.version}</version>
+            </dependency>
+
             <!-- Own dependencies  -->
             <dependency>
                 <groupId>com.alibaba.cloud</groupId>
@@ -258,6 +279,12 @@
                 <version>${revision}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-starter-alibaba-schedulerx</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
             <dependency>
                 <groupId>com.alibaba.spring</groupId>
                 <artifactId>spring-context-support</artifactId>
diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml
index 8f39d79c6..def6e1d54 100644
--- a/spring-cloud-alibaba-examples/pom.xml
+++ b/spring-cloud-alibaba-examples/pom.xml
@@ -40,7 +40,6 @@
         <module>rocketmq-example/rocketmq-example-common</module>
         <module>rocketmq-example/rocketmq-tx-example</module>
         <module>rocketmq-example/rocketmq-pollable-consume-example</module>
-
         <module>spring-cloud-bus-rocketmq-example</module>
         <module>spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example</module>
         <module>spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example</module>
@@ -52,9 +51,10 @@
         <module>integrated-example/integrated-praise-consumer</module>
         <module>integrated-example/integrated-common</module>
 		<module>integrated-example/integrated-frontend</module>
-		<module>ai-example/spring-cloud-ai-example</module>
-		<module>ai-example/spring-cloud-ai-rag-example</module>
-	</modules>
+        <module>ai-example/spring-cloud-ai-example</module>
+        <module>ai-example/spring-cloud-ai-rag-example</module>
+        <module>spring-cloud-scheduling-example</module>
+    </modules>
 
 
     <build>
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README-en.md b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README-en.md
new file mode 100644
index 000000000..2b8183a38
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README-en.md
@@ -0,0 +1,101 @@
+# Spring Cloud Alibaba Scheduling Example
+
+## Project description
+
+Spring Cloud Alibaba Scheduling provides a timing task scheduling capability based on Spring Scheduling, supporting distributed scenarios for timing task scheduling. It offers a quick integration solution for timing task scheduling services in distributed scenarios.
+
+The current offering is based on the open-source ShedLock for distributed lock acquisition, along with Alibaba Cloud's SchedulerX service [quick start](https://sca.aliyun.com/en/docs/2023/user-guide/schedulerx/quick-start/), Subsequent releases will provide access to more open-source solutions implementations.
+
+## Project dependencies
+
+### Access `spring-cloud-starter-alibaba-schedulerx`
+
+Add the following dependencies to the project `pom.xml`:
+
+   ```xml
+   <dependency>
+        <groupId>com.alibaba.cloud</groupId>
+        <artifactId>spring-cloud-starter-alibaba-schedulerx</artifactId>
+   </dependency>
+   ```
+
+## Project config description
+
+In the Example project, two types of integration configuration modes are provided for `shedlock` and `schedulerx`, Select the required access mode configuration file in `application.yaml`, the example defaults to the shedlock solution.
+
+### Solution 1. Distributed shedlock integration configuration
+
+Edit the following configuration to the `application-schedulerx.yml`:
+   ```yaml
+   spring:
+      cloud:
+         scheduling:
+            # Distributed mode: shedlock, schedulerx
+            # Set config value: shedlock
+            distributed-mode: shedlock
+      datasource:
+         driver-class: com.mysql.cj.jdbc.Driver
+         url: {jdbc_url}
+         username: {jdbc.username}
+         password: {jdbc.password}
+   ```
+You should replace `{jdbc_url}`,`{jdbc.username}`, and `{jdbc.password}` with your actual database connection information.
+
+>️ Precautions:If there's no database instance can be used, please create a database instance first.
+
+### Solution 2. Alibaba Cloud's SchedulerX integration configuration
+Edit the following configuration to the `application-schedulerx.yml`:
+   ```yaml
+   spring:
+      cloud:
+         scheduling:
+            # Distributed mode: shedlock, schedulerx
+            # Set config value: schedulerx
+            distributed-mode: schedulerx
+            schedulerx:
+               # This configuration is required, Please get it from aliyun schedulerx console
+               endpoint: acm.aliyun.com
+               namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx
+               groupId: xxxxx
+               appKey: PZm1XXXXXXXXXXXX
+               # Optional config, if you need to sync task to schedulerx
+               # task-sync: true
+               # region-id: public
+               # aliyun-access-key: XXXXXXXXXXXX
+               # aliyun-secret-key: XXXXXXXXXXXX
+               # task-model-default: standalone
+   ```
+On Alibaba Cloud service, each account has be granted a free quota for schedulerx. For detailed instructions on how to configure and use cloud product integrations, please refer to the respective product documentation. Refer to: [SchedulerX Spring Task](https://www.alibabacloud.com/help/en/schedulerx/user-guide/spring-jobs)
+
+## Start application
+
+After completing the above selection and configuration, simply run the `ScheduleApplication` class in Example to start the application. The `SimpleJob` class in this example project includes two Spring scheduled tasks that run every minute. Upon starting, you can expect to see the following logs:
+
+```text
+2024-05-17T11:20:59.981+08:00  INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-17 11:20:59 do job1...
+2024-05-17T11:20:59.985+08:00  INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-17 11:20:59 do job2...
+```
+### Distributed running verification
+
+Create two application launch configurations in IDEA, each with the startup parameter `--server.port={port}` to start the corresponding application processes. The example project defaults to using `shedlock`, 
+We can observe that `job1` will be triggered by both applications, whereas `job2` which has been annotated with `@SchedulerLock` will only be triggered in one application at the same time.
+![idea-server-port](images/idea-server-port.png)
+
+- ScheduleApplication-1, startup parameter: `--server.port=18080`, application logs:
+```text
+2024-05-20T14:02:00.003+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job1...
+2024-05-20T14:03:00.008+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job1...
+2024-05-20T14:03:00.008+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job2...
+2024-05-20T14:04:00.006+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job1...
+2024-05-20T14:04:00.010+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job2...
+2024-05-20T14:05:00.003+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job1...
+```
+- ScheduleApplication-2, startup parameter: `--server.port=18081`, application logs:
+```text
+2024-05-20T14:02:00.003+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job1...
+2024-05-20T14:02:00.008+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job2...
+2024-05-20T14:03:00.004+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job1...
+2024-05-20T14:04:00.006+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job1...
+2024-05-20T14:05:00.004+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job1...
+2024-05-20T14:05:00.007+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job2...
+```
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README.md b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README.md
new file mode 100644
index 000000000..76e7835dc
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README.md
@@ -0,0 +1,100 @@
+# Spring Cloud Alibaba Scheduling Example
+
+## 项目说明
+
+Spring Cloud Alibaba Scheduling 提供了基于 Spring Scheduling 的定时任务调度能力,支持分布式场景下的定时任务调度,为分布式场景下的定时任务调度服务提供快速接入方案。
+
+目前提供基于开源shedlock分布式抢锁模式,以及阿里云上SchedulerX服务的 [快速接入](https://sca.aliyun.com/docs/2023/user-guide/schedulerx/quick-start/) ,后续将提供更多实现的开源方案接入。
+
+## 应用依赖
+
+### 接入 `spring-cloud-starter-alibaba-schedulerx`
+
+在项目 pom.xml 中加入以下依赖:
+
+   ```xml
+   <dependency>
+        <groupId>com.alibaba.cloud</groupId>
+        <artifactId>spring-cloud-starter-alibaba-schedulerx</artifactId>
+   </dependency>
+   ```
+
+## 配置说明
+
+在Example工程中提供shedlock和schedulerx的两种接入配置模式(***二选一***),在`application.yaml`中选择需要的接入模式配置文件,案例中默认采用开源的shedlock方案。
+
+### 方案一、分布式shedlock接入配置说明
+
+在 application-schedulerx.yml 配置文件中修改以下配置:
+   ```yaml
+   spring:
+      cloud:
+         scheduling:
+            # Distributed mode: shedlock, schedulerx
+            # Set config value: shedlock
+            distributed-mode: shedlock
+      datasource:
+         driver-class: com.mysql.cj.jdbc.Driver
+         url: {jdbc_url}
+         username: {jdbc.username}
+         password: {jdbc.password}
+   ```
+使用时请需要替换`{jdbc_url}`、`{jdbc.username}`、`{jdbc.password}`为实际自有的数据库连接信息。
+
+>️ 注意:如未创建数据库,请先手动创建数据实例。
+
+### 方案二、云产品SchedulerX接入配置说明
+在 application-schedulerx.yml 配置文件中修改以下配置:
+   ```yaml
+   spring:
+      cloud:
+         scheduling:
+            # Distributed mode: shedlock, schedulerx
+            # Set config value: schedulerx
+            distributed-mode: schedulerx
+            schedulerx:
+               # This configuration is required, Please get it from aliyun schedulerx console
+               endpoint: acm.aliyun.com
+               namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx
+               groupId: xxxxx
+               appKey: PZm1XXXXXXXXXXXX
+               # Optional config, if you need to sync task to schedulerx
+               # task-sync: true
+               # region-id: public
+               # aliyun-access-key: XXXXXXXXXXXX
+               # aliyun-secret-key: XXXXXXXXXXXX
+               # task-model-default: standalone
+   ```
+阿里云上产品每个用户开通后都会有免费额度,详细云上产品接入配置使用说明,请参考:[阿里云SchedulerX Spring定时任务](https://help.aliyun.com/zh/schedulerx/user-guide/spring-jobs)
+
+## 启动应用
+
+在完成上述接入选择和配置后,直接运行Example中的 `ScheduleApplication`类即可启动运行。本案例工程中的`SimpleJob`类包含了两个每分钟执行一次的Spring定时任务,启动后可得到如下日志:
+```text
+2024-05-17T11:20:59.981+08:00  INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-17 11:20:59 do job1...
+2024-05-17T11:20:59.985+08:00  INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-17 11:20:59 do job2...
+```
+### 分布式运行验证
+
+在IDEA环境中创建两个应用启动项,各自分别配置启动参数`--server.port={应用端口}`,启动相应的应用进程。案例工程默认采用`shedlock`,
+我们可以直接看到`job1`两个应用都会同时触发,而`job2`添加了`@SchedulerLock`注解则会同一时间点只会在一个应用进程中执行。
+![idea-server-port](images/idea-server-port.png)
+
+- ScheduleApplication-1,启动参数:`--server.port=18080`,定时任务运行日志如下:
+```text
+2024-05-20T14:02:00.003+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job1...
+2024-05-20T14:03:00.008+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job1...
+2024-05-20T14:03:00.008+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job2...
+2024-05-20T14:04:00.006+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job1...
+2024-05-20T14:04:00.010+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job2...
+2024-05-20T14:05:00.003+08:00  INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job1...
+```
+- ScheduleApplication-2,启动参数:`--server.port=18081`,定时任务运行日志如下:
+```text
+2024-05-20T14:02:00.003+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job1...
+2024-05-20T14:02:00.008+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:02:00 do job2...
+2024-05-20T14:03:00.004+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:03:00 do job1...
+2024-05-20T14:04:00.006+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:04:00 do job1...
+2024-05-20T14:05:00.004+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job1...
+2024-05-20T14:05:00.007+08:00  INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob    : time=2024-05-20 14:05:00 do job2...
+```
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/images/idea-server-port.png b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/images/idea-server-port.png
new file mode 100644
index 000000000..fee23d024
Binary files /dev/null and b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/images/idea-server-port.png differ
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/pom.xml b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/pom.xml
new file mode 100644
index 000000000..7f181513a
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>spring-cloud-alibaba-examples</artifactId>
+        <groupId>com.alibaba.cloud</groupId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>spring-cloud-scheduling-example</artifactId>
+    <name>Spring Cloud Starter Alibaba Scheduling Example</name>
+    <description>Example demonstrating how to use Spring Cloud Schedule</description>
+    <packaging>jar</packaging>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-schedulerx</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- database for shedlock start, JDBC data source configuration should be added -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <!-- database for shedlock end -->
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>${maven-deploy-plugin.version}</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/ScheduleApplication.java b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/ScheduleApplication.java
new file mode 100644
index 000000000..a2aab18eb
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/ScheduleApplication.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.examples.schedule;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * ScheduleApplication.
+ *
+ * @author yaohui
+ */
+@SpringBootApplication
+@EnableScheduling
+public class ScheduleApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(ScheduleApplication.class, args);
+	}
+
+}
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/job/SimpleJob.java b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/job/SimpleJob.java
new file mode 100644
index 000000000..654e2cb81
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/job/SimpleJob.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.examples.schedule.job;
+
+import java.util.concurrent.TimeUnit;
+
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author yaohui
+ **/
+@Component
+public class SimpleJob {
+
+	private static final Logger logger = LoggerFactory.getLogger(SimpleJob.class);
+
+	/**
+	 * run without lock, all instance running at the same time.
+	 */
+	@Scheduled(cron = "0 */1 * * * ?")
+	public void job1() {
+		logger.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job1...");
+	}
+
+
+	/**
+	 * run with lock, only one instance running at the same time.
+	 *
+	 * @throws InterruptedException interrupted exception
+	 */
+	@Scheduled(cron = "0 */1 * * * ?")
+	@SchedulerLock(name = "lock-job2", lockAtMostFor = "10s")
+	public void job2() throws InterruptedException {
+		logger.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job2...");
+		TimeUnit.SECONDS.sleep(1L);
+	}
+}
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-schedulerx.yaml b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-schedulerx.yaml
new file mode 100644
index 000000000..d01f8bf75
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-schedulerx.yaml
@@ -0,0 +1,17 @@
+spring:
+  cloud:
+    scheduling:
+      # Distributed mode: shedlock, schedulerx
+      distributed-mode: schedulerx
+      schedulerx:
+        # This configuration is required, Please get it from aliyun schedulerx console
+        endpoint: acm.aliyun.com
+        namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx
+        groupId: xxxxx
+        appKey: PZm1XXXXXXXXXXXX
+        # Optional config, if you need to sync task to schedulerx
+#        task-sync: true
+#        region-id: public
+#        aliyun-access-key: XXXXXXXXXXXX
+#        aliyun-secret-key: XXXXXXXXXXXX
+#        task-model-default: standalone
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-shedlock.yaml b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-shedlock.yaml
new file mode 100644
index 000000000..e0c663a32
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-shedlock.yaml
@@ -0,0 +1,13 @@
+spring:
+  cloud:
+    scheduling:
+      # Distributed mode: shedlock, schedulerx
+      distributed-mode: shedlock
+  datasource:
+    driver-class: com.mysql.cj.jdbc.Driver
+    # Change to your database jdbc url value
+    url: jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
+    # Change to your database username value
+    username: root
+    # Change to your database password value
+    password: 123456
diff --git a/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application.yaml b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application.yaml
new file mode 100644
index 000000000..4edaaa7da
--- /dev/null
+++ b/spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+server:
+  port: 18080
+spring:
+  profiles:
+    # Select the config file to use, shedlock or schedulerx
+    active: shedlock
+  application:
+    name: spring-cloud-alibaba-schedule-example
+  # Spring task scheduling config
+  task:
+    scheduling:
+      thread-name-prefix: sca-schedule-
+      pool:
+        size: 5
+      shutdown:
+        await-termination: true
+        await-termination-period: 60s
diff --git a/spring-cloud-alibaba-starters/pom.xml b/spring-cloud-alibaba-starters/pom.xml
index a90123bdd..46366c02f 100644
--- a/spring-cloud-alibaba-starters/pom.xml
+++ b/spring-cloud-alibaba-starters/pom.xml
@@ -27,7 +27,8 @@
         <module>spring-cloud-alibaba-sentinel-gateway</module>
         <module>spring-cloud-alibaba-commons</module>
 		<module>spring-cloud-starter-alibaba-ai</module>
-	</modules>
+        <module>spring-cloud-starter-alibaba-schedulerx</module>
+    </modules>
 
     <build>
         <plugins>
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/pom.xml b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/pom.xml
new file mode 100644
index 000000000..06d7ce91f
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/pom.xml
@@ -0,0 +1,75 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.alibaba.cloud</groupId>
+        <artifactId>spring-cloud-alibaba-starters</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>spring-cloud-starter-alibaba-schedulerx</artifactId>
+    <name>Spring Cloud Starter Alibaba Scheduling</name>
+
+    <dependencies>
+
+        <!--spring boot-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.annotation</groupId>
+            <artifactId>jakarta.annotation-api</artifactId>
+        </dependency>
+
+        <!-- schedulerx dependency -->
+        <dependency>
+            <groupId>com.aliyun.schedulerx</groupId>
+            <artifactId>schedulerx2-worker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-schedulerx2</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.4.6</version>
+        </dependency>
+        <!-- schedulerx dependency end-->
+
+        <!-- shedlock dependency -->
+        <dependency>
+            <groupId>net.javacrumbs.shedlock</groupId>
+            <artifactId>shedlock-spring</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>net.javacrumbs.shedlock</groupId>
+            <artifactId>shedlock-provider-jdbc-template</artifactId>
+        </dependency>
+        <!-- shedlock dependency end -->
+    </dependencies>
+</project>
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/SchedulingConstants.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/SchedulingConstants.java
new file mode 100644
index 000000000..6445f7dfa
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/SchedulingConstants.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling;
+
+/**
+ * @author yaohui
+ **/
+public final class SchedulingConstants {
+
+	/**
+	 * Scheduling config prefix.
+	 */
+	public static final String SCHEDULING_CONFIG_PREFIX = "spring.cloud.scheduling";
+
+	/**
+	 * Scheduling distributed mode.
+	 */
+	public static final String SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY = SCHEDULING_CONFIG_PREFIX + ".distributed-mode";
+
+	private SchedulingConstants() {
+		throw new AssertionError("Must not instantiate constant utility class");
+	}
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/JobProperty.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/JobProperty.java
new file mode 100644
index 000000000..314e12de1
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/JobProperty.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx;
+
+import com.alibaba.schedulerx.common.domain.ExecuteMode;
+import com.alibaba.schedulerx.common.domain.JobType;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Job property.
+ *
+ * @author xiaomeng.hxm
+ */
+@ConfigurationProperties(prefix = SchedulerxProperties.CONFIG_PREFIX)
+public final class JobProperty {
+
+	private String jobName;
+
+	private String jobType = JobType.JAVA.getKey();
+
+	private String jobModel = ExecuteMode.STANDALONE.getKey();
+
+	private String className;
+
+	private String content;
+
+	private Integer timeType;
+
+	private String timeExpression;
+
+	private String cron;
+
+	private String oneTime;
+
+	private String jobParameter;
+
+	private String description;
+
+	private boolean overwrite = false;
+
+	public String getJobType() {
+		return jobType;
+	}
+
+	public void setJobType(String jobType) {
+		this.jobType = jobType;
+	}
+
+	public String getJobModel() {
+		return jobModel;
+	}
+
+	public void setJobModel(String jobModel) {
+		this.jobModel = jobModel;
+	}
+
+	public String getClassName() {
+		return className;
+	}
+
+	public void setClassName(String className) {
+		this.className = className;
+	}
+
+	public String getCron() {
+		return cron;
+	}
+
+	public void setCron(String cron) {
+		this.cron = cron;
+	}
+
+	public String getOneTime() {
+		return oneTime;
+	}
+
+	public void setOneTime(String oneTime) {
+		this.oneTime = oneTime;
+	}
+
+	public String getJobParameter() {
+		return jobParameter;
+	}
+
+	public void setJobParameter(String jobParameter) {
+		this.jobParameter = jobParameter;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public boolean isOverwrite() {
+		return overwrite;
+	}
+
+	public void setOverwrite(boolean overwrite) {
+		this.overwrite = overwrite;
+	}
+
+	public String getContent() {
+		return content;
+	}
+
+	public void setContent(String content) {
+		this.content = content;
+	}
+
+	public String getJobName() {
+		return jobName;
+	}
+
+	public void setJobName(String jobName) {
+		this.jobName = jobName;
+	}
+
+	public Integer getTimeType() {
+		return timeType;
+	}
+
+	public void setTimeType(Integer timeType) {
+		this.timeType = timeType;
+	}
+
+	public String getTimeExpression() {
+		return timeExpression;
+	}
+
+	public void setTimeExpression(String timeExpression) {
+		this.timeExpression = timeExpression;
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxAutoConfigure.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxAutoConfigure.java
new file mode 100644
index 000000000..261e66577
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxAutoConfigure.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx;
+
+import com.alibaba.cloud.scheduling.SchedulingConstants;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Import;
+
+/**
+ * schedulerx2 starter.
+ *
+ * @author yaohui
+ **/
+@EnableConfigurationProperties(SchedulerxProperties.class)
+@ConditionalOnProperty(name = SchedulingConstants.SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY, havingValue = "schedulerx")
+@Import({SchedulerxConfigurations.SchedulerxWorkerConfiguration.class,
+		SchedulerxConfigurations.SpringScheduleAdaptConfiguration.class})
+public class SchedulerxAutoConfigure {
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxConfigurations.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxConfigurations.java
new file mode 100644
index 000000000..4818febd4
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxConfigurations.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx;
+
+import com.alibaba.cloud.scheduling.schedulerx.service.JobSyncService;
+import com.alibaba.cloud.scheduling.schedulerx.service.ScheduledJobSyncConfigurer;
+import com.alibaba.schedulerx.common.util.ConfigUtil;
+import com.alibaba.schedulerx.common.util.StringUtils;
+import com.alibaba.schedulerx.worker.SchedulerxWorker;
+import com.alibaba.schedulerx.worker.domain.WorkerConstants;
+import com.alibaba.schedulerx.worker.log.LogFactory;
+import com.alibaba.schedulerx.worker.log.Logger;
+import com.alibaba.schedulerx.worker.processor.springscheduling.NoOpScheduler;
+import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxAnnotationBeanPostProcessor;
+import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxSchedulingConfigurer;
+import jakarta.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
+
+/**
+ * @author yaohui
+ **/
+public class SchedulerxConfigurations {
+
+	@Configuration(proxyBeanMethods = false)
+	@ConditionalOnClass(SchedulerxWorker.class)
+	@ConditionalOnProperty(prefix = SchedulerxProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
+	static class SchedulerxWorkerConfiguration {
+
+		private static final Logger LOGGER = LogFactory.getLogger(SchedulerxAutoConfigure.class);
+
+		private static final String WORKER_STARTER_SPRING_CLOUD = "springcloud";
+
+		@Autowired
+		private SchedulerxProperties properties;
+
+		@Bean
+		public JobSyncService jobSyncService() {
+			return new JobSyncService();
+		}
+
+		@PostConstruct
+		public void syncJobs() throws Exception {
+			if (!properties.getJobs().isEmpty()) {
+				LOGGER.info("{}.jobs is not empty, start to sync jobs...", SchedulerxProperties.CONFIG_PREFIX);
+				jobSyncService().syncJobs();
+				LOGGER.info("sync jobs finished.");
+			}
+		}
+
+		@Bean
+		public SchedulerxWorker schedulerxWorker() {
+			SchedulerxWorker schedulerxWorker = new SchedulerxWorker();
+			schedulerxWorker.setDomainName(properties.getDomainName());
+			schedulerxWorker.setGroupId(properties.getGroupId());
+			schedulerxWorker.setEnableBatchWork(properties.isEnableBatchWork());
+			schedulerxWorker.setDisableSites(properties.getDisableSites());
+			schedulerxWorker.setEnableSites(properties.getEnableSites());
+			schedulerxWorker.setDisableUnits(properties.getDisableUnits());
+			schedulerxWorker.setEnableUnits(properties.getEnableUnits());
+			schedulerxWorker.setAppKey(properties.getAppKey());
+			schedulerxWorker.setAliyunAccessKey(properties.getAliyunAccessKey());
+			schedulerxWorker.setAliyunSecretKey(properties.getAliyunSecretKey());
+			schedulerxWorker.setNamespace(properties.getNamespace());
+			schedulerxWorker.setHost(properties.getHost());
+			schedulerxWorker.setPort(properties.getPort());
+			schedulerxWorker.setEndpoint(properties.getEndpoint());
+			schedulerxWorker.setNamespaceSource(properties.getNamespaceSource());
+			schedulerxWorker.setMaxTaskBodySize(properties.getMaxTaskBodySize());
+			schedulerxWorker.setBlockAppStart(properties.isBlockAppStart());
+			schedulerxWorker.setSTSAccessKey(properties.getStsAccessKey());
+			schedulerxWorker.setSTSSecretKey(properties.getStsSecretKey());
+			schedulerxWorker.setSTSSecretToken(properties.getStsToken());
+			schedulerxWorker.setSlsCollectorEnable(properties.isSlsCollectorEnable());
+			schedulerxWorker.setShareContainerPool(properties.isShareContainerPool());
+			schedulerxWorker.setThreadPoolMode(properties.getThreadPoolMode());
+			schedulerxWorker.setLabel(properties.getLabel());
+			schedulerxWorker.setLabelPath(properties.getLabelPath());
+			if (properties.isShareContainerPool() || WorkerConstants.THREAD_POOL_MODE_ALL.equals(properties.getThreadPoolMode())) {
+				schedulerxWorker.setSharePoolSize(properties.getSharePoolSize());
+				schedulerxWorker.setSharePoolQueueSize(properties.getSharePoolQueueSize());
+			}
+			if (StringUtils.isNotEmpty(properties.getEndpointPort())) {
+				schedulerxWorker.setEndpointPort(Integer.parseInt(properties.getEndpointPort()));
+			}
+			schedulerxWorker.setEnableCgroupMetrics(properties.isEnableCgroupMetrics());
+			if (properties.isEnableCgroupMetrics()) {
+				schedulerxWorker.setCgroupPathPrefix(properties.getCgroupPathPrefix());
+			}
+			if (StringUtils.isNotEmpty(properties.getNamespaceSource())) {
+				schedulerxWorker.setNamespaceSource(properties.getNamespaceSource());
+			}
+			schedulerxWorker.setAkkaRemotingAutoRecover(properties.isAkkaRemotingAutoRecover());
+			schedulerxWorker.setEnableHeartbeatLog(properties.isEnableHeartbeatLog());
+			schedulerxWorker.setMapMasterStatusCheckInterval(properties.getMapMasterStatusCheckInterval());
+			schedulerxWorker.setEnableSecondDelayCycleIntervalMs(properties.isEnableSecondDealyCycleIntervalMs());
+			schedulerxWorker.setEnableMapMasterFailover(properties.isEnableMapMasterFailover());
+			schedulerxWorker.setEnableSecondDelayStandaloneDispatch(properties.isEnableSecondDelayStandaloneDispatch());
+			schedulerxWorker.setPageSize(properties.getPageSize());
+			schedulerxWorker.setGraceShutdownMode(properties.getGraceShutdownMode());
+			if (properties.getGraceShutdownTimeout() > 0) {
+				schedulerxWorker.setGraceShutdownTimeout(properties.getGraceShutdownTimeout());
+			}
+			schedulerxWorker.setBroadcastDispatchThreadNum(properties.getBroadcastDispatchThreadNum());
+			schedulerxWorker.setBroadcastDispatchThreadEnable(properties.isBroadcastDispatchThreadEnable());
+			schedulerxWorker.setBroadcastMasterExecEnable(properties.isBroadcastMasterExecEnable());
+			schedulerxWorker.setBroadcastDispatchRetryTimes(properties.getBroadcastDispatchRetryTimes());
+			schedulerxWorker.setProcessorPoolSize(properties.getProcessorPoolSize());
+			schedulerxWorker.setMapMasterDispatchRandom(properties.isMapMasterDispatchRandom());
+			schedulerxWorker.setMapMasterRouterStrategy(properties.getMapMasterRouterStrategy());
+			if (StringUtils.isNotEmpty(properties.getH2DatabaseUser())) {
+				schedulerxWorker.setH2DatabaseUser(properties.getH2DatabaseUser());
+			}
+			if (StringUtils.isNotEmpty(properties.getH2DatabasePassword())) {
+				schedulerxWorker.setH2DatabasePassword(properties.getH2DatabasePassword());
+			}
+			schedulerxWorker.setHttpServerEnable(properties.getHttpServerEnable());
+			schedulerxWorker.setHttpServerPort(properties.getHttpServerPort());
+			if (properties.getMaxMapDiskPercent() != null) {
+				schedulerxWorker.setMaxMapDiskPercent(properties.getMaxMapDiskPercent());
+			}
+
+			ConfigUtil.getWorkerConfig().setProperty(WorkerConstants.WORKER_STARTER_MODE,
+					WORKER_STARTER_SPRING_CLOUD);
+			return schedulerxWorker;
+		}
+	}
+
+
+	@Configuration(proxyBeanMethods = false)
+	@AutoConfigureAfter(SchedulerxWorkerConfiguration.class)
+	@ConditionalOnBean(SchedulerxWorker.class)
+	static class SpringScheduleAdaptConfiguration {
+
+		@Bean(ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME)
+		public NoOpScheduler noOpScheduler() {
+			return new NoOpScheduler();
+		}
+
+		@Bean
+		public SchedulerxSchedulingConfigurer schedulerxSchedulingConfigurer() {
+			return new SchedulerxSchedulingConfigurer();
+		}
+
+		@Bean
+		public SchedulerxAnnotationBeanPostProcessor schedulerxAnnotationBeanPostProcessor() {
+			return new SchedulerxAnnotationBeanPostProcessor();
+		}
+
+		@Bean
+		@ConditionalOnProperty(prefix = SchedulerxProperties.CONFIG_PREFIX, name = "task-sync", havingValue = "true")
+		public ScheduledJobSyncConfigurer scheduledJobSyncConfigurer() {
+			return new ScheduledJobSyncConfigurer();
+		}
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxProperties.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxProperties.java
new file mode 100644
index 000000000..d5fd9afeb
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxProperties.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.alibaba.cloud.scheduling.SchedulingConstants;
+import com.alibaba.schedulerx.common.domain.ContactInfo;
+import com.alibaba.schedulerx.common.util.JsonUtil;
+import com.alibaba.schedulerx.worker.domain.WorkerConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * schedulerx worker properties.
+ *
+ * @author yaohui
+ **/
+@ConfigurationProperties(prefix = SchedulerxProperties.CONFIG_PREFIX)
+public class SchedulerxProperties implements InitializingBean {
+
+	private static final Logger logger = LoggerFactory.getLogger(SchedulerxProperties.class);
+
+	/**
+	 * schedulerx config prefix.
+	 */
+	public static final String CONFIG_PREFIX = SchedulingConstants.SCHEDULING_CONFIG_PREFIX + ".schedulerx";
+
+	/**
+	 * domainName.
+	 */
+	private String domainName;
+
+	/**
+	 * groupId.
+	 */
+	private String groupId;
+
+	/**
+	 * host.
+	 */
+	private String host;
+
+	/**
+	 * client port.
+	 */
+	private int port = 0;
+
+	private String enableUnits;
+
+	private String disableUnits;
+
+	private String enableSites;
+
+	private String disableSites;
+
+	private boolean enableBatchWork;
+
+	/**
+	 * enabled: true; false.
+	 */
+	private boolean enabled = true;
+
+	/**
+	 * appName.
+	 */
+	private String appName;
+
+	/**
+	 * appKey.
+	 */
+	private String appKey;
+
+	/**
+	 * aliyunRamRole.
+	 */
+	private String aliyunRamRole;
+
+	/**
+	 * aliyunAccessKey.
+	 */
+	private String aliyunAccessKey;
+	/**
+	 * aliyunSecretKey.
+	 */
+	private String aliyunSecretKey;
+
+	/**
+	 * STS ak.
+	 */
+	private String stsAccessKey;
+
+	/**
+	 * STS sk.
+	 */
+	private String stsSecretKey;
+
+	/**
+	 * STS secret token.
+	 */
+	private String stsToken;
+
+	/**
+	 * Namespace UID.
+	 */
+	private String namespace;
+
+	/**
+	 * endpoint.
+	 */
+	private String endpoint;
+
+	/**
+	 * endpointPort.
+	 */
+	private String endpointPort;
+
+	/**
+	 * namespaceName.
+	 */
+	private String namespaceName;
+
+	/**
+	 * namespaceSource.
+	 */
+	private String namespaceSource;
+
+	/**
+	 * maxTaskBodySize (byte).
+	 */
+	private int maxTaskBodySize = WorkerConstants.TASK_BODY_SIZE_MAX_DEFAULT;
+
+	private boolean blockAppStart = true;
+
+	/**
+	 * slsCollectorEnable.
+	 */
+	private boolean slsCollectorEnable = true;
+
+	/**
+	 * shareContainerPool.
+	 */
+	private boolean shareContainerPool = false;
+
+	/**
+	 * threadPoolMode.
+	 */
+	private String threadPoolMode;
+
+	/**
+	 * sharePoolSize.
+	 */
+	private int sharePoolSize = WorkerConstants.SHARE_POOL_SIZE_DEFAULT;
+
+	/**
+	 * sharePoolQueueSize.
+	 */
+	private int sharePoolQueueSize = Integer.MAX_VALUE;
+
+	/**
+	 * Wlabel.
+	 */
+	private String label;
+
+	private String labelPath = "/etc/podinfo/annotations";
+
+	/**
+	 * enableCgroupMetrics.
+	 */
+	private boolean enableCgroupMetrics = false;
+
+	/**
+	 * cgroupPathPrefix.
+	 */
+	private String cgroupPathPrefix = "/sys/fs/cgroup/cpu/";
+
+	/**
+	 * akkaRemotingAutoRecover.
+	 */
+	private boolean akkaRemotingAutoRecover = true;
+
+	/**
+	 * enableHeartbeatLog.
+	 */
+	private boolean enableHeartbeatLog = true;
+
+	/**
+	 * mapMasterStatusCheckInterval(ms).
+	 */
+	private int mapMasterStatusCheckInterval = WorkerConstants.Map_MASTER_STATUS_CHECK_INTERVAL_DEFAULT;
+
+	/**
+	 * enableSecondDealyCycleIntervalMs.
+	 */
+	private boolean enableSecondDealyCycleIntervalMs = false;
+
+	/**
+	 * enableMapMasterFailover.
+	 */
+	private boolean enableMapMasterFailover = true;
+
+	/**
+	 * enableSecondDelayStandaloneDispatch.
+	 */
+	private boolean enableSecondDelayStandaloneDispatch = false;
+
+	/**
+	 * pageSize.
+	 */
+	private int pageSize = 1000;
+
+	/**
+	 * GraceShutdownMode(WAIT_ALL; WAIT_RUNNING;).
+	 */
+	private String graceShutdownMode;
+
+	/**
+	 * graceShutdownTimeout.
+	 */
+	private long graceShutdownTimeout = WorkerConstants.GRACE_SHUTDOWN_TIMEOUT_DEFAULT;
+
+	/**
+	 * broadcastDispatchThreadNum.
+	 */
+	private int broadcastDispatchThreadNum = 4;
+
+	/**
+	 * broadcastDispatchRetryTimes.
+	 */
+	private int broadcastDispatchRetryTimes = 1;
+
+	/**
+	 * broadcastDispatchThreadEnable.
+	 */
+	private boolean broadcastDispatchThreadEnable = false;
+
+	/**
+	 * broadcastMasterExecEnable.
+	 */
+	private boolean broadcastMasterExecEnable = true;
+
+	/**
+	 * mapMasterDispatchRandom.
+	 */
+	private boolean mapMasterDispatchRandom = false;
+
+	private Integer mapMasterRouterStrategy;
+
+	private String regionId;
+
+	/**
+	 * h2DatabaseUser.
+	 */
+	private String h2DatabaseUser;
+
+	/**
+	 * h2DatabasePassword.
+	 */
+	private String h2DatabasePassword;
+
+	/**
+	 * httpServerEnable.
+	 */
+	private Boolean httpServerEnable;
+
+	/**
+	 * httpServerPort.
+	 */
+	private Integer httpServerPort;
+
+	/**
+	 * maxMapDiskPercent.
+	 */
+	private Float maxMapDiskPercent;
+
+	private Map<String, JobProperty> jobs = new LinkedHashMap<>();
+
+	private String alarmChannel;
+
+	private Map<String, ContactInfo> alarmUsers = new LinkedHashMap<>();
+
+	private Map<String, Integer> processorPoolSize = new HashMap<>();
+
+	public String getDomainName() {
+		return domainName;
+	}
+
+	public void setDomainName(String domainName) {
+		this.domainName = domainName;
+	}
+
+	public String getGroupId() {
+		return groupId;
+	}
+
+	public void setGroupId(String groupId) {
+		this.groupId = groupId;
+	}
+
+	public boolean isEnabled() {
+		return enabled;
+	}
+
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+	}
+
+	public String getHost() {
+		return host;
+	}
+
+	public void setHost(String host) {
+		this.host = host;
+	}
+
+	public int getPort() {
+		return port;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	public String getEnableUnits() {
+		return enableUnits;
+	}
+
+	public void setEnableUnits(String enableUnits) {
+		this.enableUnits = enableUnits;
+	}
+
+	public String getDisableUnits() {
+		return disableUnits;
+	}
+
+	public void setDisableUnits(String disableUnits) {
+		this.disableUnits = disableUnits;
+	}
+
+	public String getEnableSites() {
+		return enableSites;
+	}
+
+	public void setEnableSites(String enableSites) {
+		this.enableSites = enableSites;
+	}
+
+	public String getDisableSites() {
+		return disableSites;
+	}
+
+	public void setDisableSites(String disableSites) {
+		this.disableSites = disableSites;
+	}
+
+	public boolean isEnableBatchWork() {
+		return enableBatchWork;
+	}
+
+	public void setEnableBatchWork(boolean enableBatchWork) {
+		this.enableBatchWork = enableBatchWork;
+	}
+
+	public String getAliyunAccessKey() {
+		return aliyunAccessKey;
+	}
+
+	public void setAliyunAccessKey(String aliyunAccessKey) {
+		this.aliyunAccessKey = aliyunAccessKey;
+	}
+
+	public String getAliyunSecretKey() {
+		return aliyunSecretKey;
+	}
+
+	public void setAliyunSecretKey(String aliyunSecretKey) {
+		this.aliyunSecretKey = aliyunSecretKey;
+	}
+
+	public String getNamespace() {
+		return namespace;
+	}
+
+	public void setNamespace(String namespace) {
+		this.namespace = namespace;
+	}
+
+	public String getEndpoint() {
+		return endpoint;
+	}
+
+	public void setEndpoint(String endpoint) {
+		this.endpoint = endpoint;
+	}
+
+	public String getEndpointPort() {
+		return endpointPort;
+	}
+
+	public void setEndpointPort(String endpointPort) {
+		this.endpointPort = endpointPort;
+	}
+
+	public String getNamespaceName() {
+		return namespaceName;
+	}
+
+	public void setNamespaceName(String namespaceName) {
+		this.namespaceName = namespaceName;
+	}
+
+	public String getNamespaceSource() {
+		return namespaceSource;
+	}
+
+	public void setNamespaceSource(String namespaceSource) {
+		this.namespaceSource = namespaceSource;
+	}
+
+	public int getMaxTaskBodySize() {
+		return maxTaskBodySize;
+	}
+
+	public void setMaxTaskBodySize(int maxTaskBodySize) {
+		this.maxTaskBodySize = maxTaskBodySize;
+	}
+
+	public boolean isBlockAppStart() {
+		return blockAppStart;
+	}
+
+	public void setBlockAppStart(boolean blockAppStart) {
+		this.blockAppStart = blockAppStart;
+	}
+
+	public String getAppName() {
+		return appName;
+	}
+
+	public void setAppName(String appName) {
+		this.appName = appName;
+	}
+
+	public String getAppKey() {
+		return appKey;
+	}
+
+	public void setAppKey(String appKey) {
+		this.appKey = appKey;
+	}
+
+	public String getStsAccessKey() {
+		return stsAccessKey;
+	}
+
+	public void setStsAccessKey(String stsAccessKey) {
+		this.stsAccessKey = stsAccessKey;
+	}
+
+	public String getStsSecretKey() {
+		return stsSecretKey;
+	}
+
+	public void setStsSecretKey(String stsSecretKey) {
+		this.stsSecretKey = stsSecretKey;
+	}
+
+	public String getStsToken() {
+		return stsToken;
+	}
+
+	public String getAliyunRamRole() {
+		return aliyunRamRole;
+	}
+
+	public void setAliyunRamRole(String aliyunRamRole) {
+		this.aliyunRamRole = aliyunRamRole;
+	}
+
+	public void setStsToken(String stsToken) {
+		this.stsToken = stsToken;
+	}
+
+	public boolean isSlsCollectorEnable() {
+		return slsCollectorEnable;
+	}
+
+	public void setSlsCollectorEnable(boolean slsCollectorEnable) {
+		this.slsCollectorEnable = slsCollectorEnable;
+	}
+
+	public boolean isShareContainerPool() {
+		return shareContainerPool;
+	}
+
+	public void setShareContainerPool(boolean shareContainerPool) {
+		this.shareContainerPool = shareContainerPool;
+	}
+
+	public int getSharePoolSize() {
+		return sharePoolSize;
+	}
+
+	public void setSharePoolSize(int sharePoolSize) {
+		this.sharePoolSize = sharePoolSize;
+	}
+
+	public String getLabel() {
+		return label;
+	}
+
+	public void setLabel(String label) {
+		if (label != null) {
+			if (label.startsWith("#") && label.endsWith("#")) {
+				String labelKey = label.substring(1, label.length() - 1);
+				this.label = System.getenv(labelKey);
+				return;
+			}
+		}
+		this.label = label;
+	}
+
+	public String getLabelPath() {
+		return labelPath;
+	}
+
+	public void setLabelPath(String labelPath) {
+		this.labelPath = labelPath;
+	}
+
+	public boolean isEnableCgroupMetrics() {
+		return enableCgroupMetrics;
+	}
+
+	public void setEnableCgroupMetrics(boolean enableCgroupMetrics) {
+		this.enableCgroupMetrics = enableCgroupMetrics;
+	}
+
+	public String getCgroupPathPrefix() {
+		return cgroupPathPrefix;
+	}
+
+	public void setCgroupPathPrefix(String cgroupPathPrefix) {
+		this.cgroupPathPrefix = cgroupPathPrefix;
+	}
+
+	public boolean isAkkaRemotingAutoRecover() {
+		return akkaRemotingAutoRecover;
+	}
+
+	public void setAkkaRemotingAutoRecover(boolean akkaRemotingAutoRecover) {
+		this.akkaRemotingAutoRecover = akkaRemotingAutoRecover;
+	}
+
+	public boolean isEnableHeartbeatLog() {
+		return enableHeartbeatLog;
+	}
+
+	public void setEnableHeartbeatLog(boolean enableHeartbeatLog) {
+		this.enableHeartbeatLog = enableHeartbeatLog;
+	}
+
+	public int getMapMasterStatusCheckInterval() {
+		return mapMasterStatusCheckInterval;
+	}
+
+	public void setMapMasterStatusCheckInterval(int mapMasterStatusCheckInterval) {
+		this.mapMasterStatusCheckInterval = mapMasterStatusCheckInterval;
+	}
+
+	public boolean isEnableSecondDealyCycleIntervalMs() {
+		return enableSecondDealyCycleIntervalMs;
+	}
+
+	public void setEnableSecondDealyCycleIntervalMs(boolean enableSecondDealyCycleIntervalMs) {
+		this.enableSecondDealyCycleIntervalMs = enableSecondDealyCycleIntervalMs;
+	}
+
+	public boolean isEnableMapMasterFailover() {
+		return enableMapMasterFailover;
+	}
+
+	public void setEnableMapMasterFailover(boolean enableMapMasterFailover) {
+		this.enableMapMasterFailover = enableMapMasterFailover;
+	}
+
+	public boolean isEnableSecondDelayStandaloneDispatch() {
+		return enableSecondDelayStandaloneDispatch;
+	}
+
+	public void setEnableSecondDelayStandaloneDispatch(boolean enableSecondDelayStandaloneDispatch) {
+		this.enableSecondDelayStandaloneDispatch = enableSecondDelayStandaloneDispatch;
+	}
+
+	public int getPageSize() {
+		return pageSize;
+	}
+
+	public String getGraceShutdownMode() {
+		return graceShutdownMode;
+	}
+
+	public void setGraceShutdownMode(String graceShutdownMode) {
+		this.graceShutdownMode = graceShutdownMode;
+	}
+
+	public long getGraceShutdownTimeout() {
+		return graceShutdownTimeout;
+	}
+
+	public void setGraceShutdownTimeout(long graceShutdownTimeout) {
+		this.graceShutdownTimeout = graceShutdownTimeout;
+	}
+
+	public void setPageSize(int pageSize) {
+		this.pageSize = pageSize;
+	}
+
+	public String getRegionId() {
+		return regionId;
+	}
+
+	public void setRegionId(String regionId) {
+		this.regionId = regionId;
+	}
+
+	public Map<String, JobProperty> getJobs() {
+		return jobs;
+	}
+
+	public void setJobs(Map<String, JobProperty> jobs) {
+		this.jobs = jobs;
+	}
+
+	public String getAlarmChannel() {
+		return alarmChannel;
+	}
+
+	public void setAlarmChannel(String alarmChannel) {
+		this.alarmChannel = alarmChannel;
+	}
+
+	public Map<String, ContactInfo> getAlarmUsers() {
+		return alarmUsers;
+	}
+
+	public void setAlarmUsers(Map<String, ContactInfo> alarmUsers) {
+		this.alarmUsers = alarmUsers;
+	}
+
+	public int getBroadcastDispatchThreadNum() {
+		return broadcastDispatchThreadNum;
+	}
+
+	public void setBroadcastDispatchThreadNum(int broadcastDispatchThreadNum) {
+		this.broadcastDispatchThreadNum = broadcastDispatchThreadNum;
+	}
+
+	public boolean isBroadcastDispatchThreadEnable() {
+		return broadcastDispatchThreadEnable;
+	}
+
+	public void setBroadcastDispatchThreadEnable(boolean broadcastDispatchThreadEnable) {
+		this.broadcastDispatchThreadEnable = broadcastDispatchThreadEnable;
+	}
+
+	public String getThreadPoolMode() {
+		return threadPoolMode;
+	}
+
+	public void setThreadPoolMode(String threadPoolMode) {
+		this.threadPoolMode = threadPoolMode;
+	}
+
+	public Map<String, Integer> getProcessorPoolSize() {
+		return processorPoolSize;
+	}
+
+	public void setProcessorPoolSize(Map<String, Integer> processorPoolSize) {
+		this.processorPoolSize = processorPoolSize;
+	}
+
+	public int getSharePoolQueueSize() {
+		return sharePoolQueueSize;
+	}
+
+	public void setSharePoolQueueSize(int sharePoolQueueSize) {
+		this.sharePoolQueueSize = sharePoolQueueSize;
+	}
+
+	public boolean isMapMasterDispatchRandom() {
+		return mapMasterDispatchRandom;
+	}
+
+	public void setMapMasterDispatchRandom(boolean mapMasterDispatchRandom) {
+		this.mapMasterDispatchRandom = mapMasterDispatchRandom;
+	}
+
+	public boolean isBroadcastMasterExecEnable() {
+		return broadcastMasterExecEnable;
+	}
+
+	public void setBroadcastMasterExecEnable(boolean broadcastMasterExecEnable) {
+		this.broadcastMasterExecEnable = broadcastMasterExecEnable;
+	}
+
+	public int getBroadcastDispatchRetryTimes() {
+		return broadcastDispatchRetryTimes;
+	}
+
+	public void setBroadcastDispatchRetryTimes(int broadcastDispatchRetryTimes) {
+		this.broadcastDispatchRetryTimes = broadcastDispatchRetryTimes;
+	}
+
+	public Integer getMapMasterRouterStrategy() {
+		return mapMasterRouterStrategy;
+	}
+
+	public void setMapMasterRouterStrategy(Integer mapMasterRouterStrategy) {
+		this.mapMasterRouterStrategy = mapMasterRouterStrategy;
+	}
+
+	public String getH2DatabaseUser() {
+		return h2DatabaseUser;
+	}
+
+	public void setH2DatabaseUser(String h2DatabaseUser) {
+		this.h2DatabaseUser = h2DatabaseUser;
+	}
+
+	public String getH2DatabasePassword() {
+		return h2DatabasePassword;
+	}
+
+	public void setH2DatabasePassword(String h2DatabasePassword) {
+		this.h2DatabasePassword = h2DatabasePassword;
+	}
+
+	public Boolean getHttpServerEnable() {
+		return httpServerEnable;
+	}
+
+	public void setHttpServerEnable(Boolean httpServerEnable) {
+		this.httpServerEnable = httpServerEnable;
+	}
+
+	public Integer getHttpServerPort() {
+		return httpServerPort;
+	}
+
+	public void setHttpServerPort(Integer httpServerPort) {
+		this.httpServerPort = httpServerPort;
+	}
+
+	public Float getMaxMapDiskPercent() {
+		return maxMapDiskPercent;
+	}
+
+	public void setMaxMapDiskPercent(float maxMapDiskPercent) {
+		this.maxMapDiskPercent = maxMapDiskPercent;
+	}
+
+	@Override
+	public void afterPropertiesSet() {
+		logger.info("SchedulerxProperties->" + JsonUtil.toJson(this));
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/constants/SchedulerxConstants.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/constants/SchedulerxConstants.java
new file mode 100644
index 000000000..0710b1ba8
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/constants/SchedulerxConstants.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx.constants;
+
+/**
+ * Schedulerx constants.
+ *
+ * @author yaohui
+ */
+public final class SchedulerxConstants {
+
+	/**
+	 * Schedulerx default namespace source.
+	 */
+	public static final String NAMESPACE_SOURCE_SPRINGBOOT = "springboot";
+
+	/**
+	 * Aliyun pop product.
+	 */
+	public static final String ALIYUN_POP_PRODUCT = "schedulerx2";
+
+	/**
+	 * Aliyun pop endpoint.
+	 */
+	public static final String ALIYUN_POP_SCHEDULERX_ENDPOINT = "schedulerx.aliyuncs.com";
+
+	/**
+	 * Second delay max value.
+	 */
+	public static final int SECOND_DELAY_MAX_VALUE = 60;
+
+	/**
+	 * Second delay min value.
+	 */
+	public static final int SECOND_DELAY_MIN_VALUE = 1;
+
+	/**
+	 * Job timeout default value.
+	 */
+	public static final long JOB_TIMEOUT_DEFAULT = 3600L;
+
+	/**
+	 * Job retry count default value.
+	 */
+	public static final int JOB_RETRY_COUNT_DEFAULT = 3;
+
+	/**
+	 * Job retry interval default value.
+	 */
+	public static final int JOB_RETRY_INTERVAL_DEFAULT = 30;
+
+	/**
+	 * Job alarm channel default value.
+	 */
+	public static final String JOB_ALARM_CHANNEL_DEFAULT = "default";
+
+	/**
+	 * Job model mapreduce alias.
+	 */
+	public static final String JOB_MODEL_MAPREDUCE_ALIAS = "mapreduce";
+
+	@Override
+	public String toString() {
+		return super.toString();
+	}
+
+	private SchedulerxConstants() {
+		throw new AssertionError("Must not instantiate constant utility class");
+	}
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/JobSyncService.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/JobSyncService.java
new file mode 100644
index 000000000..67e6449fc
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/JobSyncService.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import com.alibaba.cloud.scheduling.schedulerx.JobProperty;
+import com.alibaba.cloud.scheduling.schedulerx.SchedulerxProperties;
+import com.alibaba.cloud.scheduling.schedulerx.constants.SchedulerxConstants;
+import com.alibaba.cloud.scheduling.schedulerx.util.CronExpression;
+import com.alibaba.schedulerx.common.domain.ContactInfo;
+import com.alibaba.schedulerx.common.domain.ExecuteMode;
+import com.alibaba.schedulerx.common.domain.JobType;
+import com.alibaba.schedulerx.common.domain.TimeType;
+import com.alibaba.schedulerx.common.sdk.common.MonitorConfig;
+import com.alibaba.schedulerx.common.util.JsonUtil;
+import com.alibaba.schedulerx.common.util.StringUtils;
+import com.alibaba.schedulerx.worker.log.LogFactory;
+import com.alibaba.schedulerx.worker.log.Logger;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.auth.InstanceProfileCredentialsProvider;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateAppGroupRequest;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateAppGroupResponse;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateJobRequest;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateJobResponse;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateNamespaceRequest;
+import com.aliyuncs.schedulerx2.model.v20190430.CreateNamespaceResponse;
+import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoRequest;
+import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoResponse;
+import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoResponse.Data.JobConfigInfo;
+import com.aliyuncs.schedulerx2.model.v20190430.UpdateJobRequest;
+import com.aliyuncs.schedulerx2.model.v20190430.UpdateJobResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * JobSyncService.
+ *
+ * @author xiaomeng.hxm
+ */
+public class JobSyncService {
+
+	private static final Logger logger = LogFactory.getLogger(JobSyncService.class);
+
+	@Autowired
+	private SchedulerxProperties properties;
+
+	private DefaultAcsClient client;
+
+	private synchronized DefaultAcsClient getClient() {
+		// build aliyun pop client
+		if (client == null) {
+			DefaultProfile.addEndpoint(properties.getRegionId(), SchedulerxConstants.ALIYUN_POP_PRODUCT, SchedulerxConstants.ALIYUN_POP_SCHEDULERX_ENDPOINT);
+			if (StringUtils.isNotEmpty(properties.getAliyunRamRole())) {
+				DefaultProfile profile = DefaultProfile.getProfile(properties.getRegionId());
+				InstanceProfileCredentialsProvider provider = new InstanceProfileCredentialsProvider(
+						properties.getAliyunRamRole());
+				client = new DefaultAcsClient(profile, provider);
+			}
+			else {
+				DefaultProfile defaultProfile = DefaultProfile.getProfile(properties.getRegionId(),
+						properties.getAliyunAccessKey(),
+						properties.getAliyunSecretKey());
+				client = new DefaultAcsClient(defaultProfile);
+			}
+		}
+		return client;
+	}
+
+	/**
+	 * Sync job config.
+	 *
+	 * @param jobs            job configs
+	 * @param namespaceSource namespace source
+	 * @throws Exception sync job config exception
+	 */
+	public synchronized void syncJobs(Map<String, JobProperty> jobs, String namespaceSource) throws Exception {
+		DefaultAcsClient client = getClient();
+		for (Entry<String, JobProperty> entry : jobs.entrySet()) {
+			String jobName = entry.getKey();
+			JobProperty jobProperty = entry.getValue();
+			JobConfigInfo jobConfigInfo = getJob(client, jobName, namespaceSource);
+			if (jobConfigInfo == null) {
+				createJob(client, jobName, jobProperty, namespaceSource);
+			}
+			else if (jobProperty.isOverwrite()) {
+				updateJob(client, jobConfigInfo, jobProperty, namespaceSource);
+			}
+		}
+	}
+
+	/**
+	 * sync jobs.
+	 *
+	 * @throws Exception sync jobs exception
+	 */
+	public void syncJobs() throws Exception {
+		// 1. create namespace
+		if (syncNamespace(getClient())) {
+			// 2. create app group
+			if (syncAppGroup(getClient())) {
+				syncJobs(properties.getJobs(), getNamespaceSource());
+				properties.setNamespaceSource(getNamespaceSource());
+			}
+		}
+	}
+
+	/**
+	 * sync namespace.
+	 *
+	 * @param client pop client
+	 * @return true if success
+	 * @throws Exception sync namespace exception
+	 */
+	public boolean syncNamespace(DefaultAcsClient client) throws Exception {
+		if (StringUtils.isEmpty(properties.getNamespace())) {
+			logger.error("please set {}.namespace", SchedulerxProperties.CONFIG_PREFIX);
+			throw new IOException(String.format("please set %s.namespace", SchedulerxProperties.CONFIG_PREFIX));
+		}
+
+		if (StringUtils.isEmpty(properties.getNamespaceName())) {
+			logger.error("please set {}.namespaceName", SchedulerxProperties.CONFIG_PREFIX);
+			throw new IOException(String.format("please set %s.namespaceName", SchedulerxProperties.CONFIG_PREFIX));
+		}
+
+		CreateNamespaceRequest request = new CreateNamespaceRequest();
+		request.setUid(properties.getNamespace());
+		request.setName(properties.getNamespaceName());
+		request.setSource(getNamespaceSource());
+		CreateNamespaceResponse response = client.getAcsResponse(request);
+		if (response.getSuccess()) {
+			logger.info(JsonUtil.toJson(response));
+			return true;
+		}
+		else {
+			throw new IOException(response.getMessage());
+		}
+
+	}
+
+	/**
+	 * sync app group.
+	 *
+	 * @param client pop client
+	 * @return sync app group result
+	 * @throws IOException     sync app group exception.
+	 * @throws ClientException sync app group pop client exception.
+	 */
+	public boolean syncAppGroup(DefaultAcsClient client) throws IOException, ClientException {
+		if (StringUtils.isEmpty(properties.getAppName())) {
+			logger.error("please set {}.appName", SchedulerxProperties.CONFIG_PREFIX);
+			throw new IOException(String.format("please set %s.appName", SchedulerxProperties.CONFIG_PREFIX));
+		}
+
+		if (StringUtils.isEmpty(properties.getAppKey())) {
+			logger.error("please set {}.appKey", SchedulerxProperties.CONFIG_PREFIX);
+			throw new IOException(String.format("please set %s.appKey", SchedulerxProperties.CONFIG_PREFIX));
+		}
+
+		if (StringUtils.isEmpty(properties.getGroupId())) {
+			logger.error("please set {}.groupId", SchedulerxProperties.CONFIG_PREFIX);
+			throw new IOException(String.format("please set %s.groupId", SchedulerxProperties.CONFIG_PREFIX));
+		}
+
+		CreateAppGroupRequest request = new CreateAppGroupRequest();
+		request.setNamespace(properties.getNamespace());
+		request.setNamespaceSource(getNamespaceSource());
+		request.setAppName(properties.getAppName());
+		request.setGroupId(properties.getGroupId());
+		request.setAppKey(properties.getAppKey());
+		if (StringUtils.isNotEmpty(properties.getAlarmChannel())) {
+			MonitorConfig monitorConfig = new MonitorConfig();
+			monitorConfig.setSendChannel(properties.getAlarmChannel());
+			request.setMonitorConfigJson(JsonUtil.toJson(monitorConfig));
+		}
+		if (!properties.getAlarmUsers().isEmpty()) {
+			List<ContactInfo> contactInfos = new ArrayList(properties.getAlarmUsers().values());
+			request.setMonitorContactsJson(JsonUtil.toJson(contactInfos));
+		}
+		CreateAppGroupResponse response = client.getAcsResponse(request);
+		if (response.getSuccess()) {
+			logger.info(JsonUtil.toJson(response));
+			return true;
+		}
+		else {
+			throw new IOException(response.getMessage());
+		}
+	}
+
+	/**
+	 * Get job config info.
+	 *
+	 * @param client          pop client
+	 * @param jobName         job name
+	 * @param namespaceSource namespace source
+	 * @return job config info.
+	 * @throws Exception get job config info exception.
+	 */
+	private JobConfigInfo getJob(DefaultAcsClient client, String jobName, String namespaceSource) throws Exception {
+		GetJobInfoRequest request = new GetJobInfoRequest();
+		request.setNamespace(properties.getNamespace());
+		request.setNamespaceSource(namespaceSource);
+		request.setGroupId(properties.getGroupId());
+		request.setJobId(0L);
+		request.setJobName(jobName);
+		GetJobInfoResponse response = client.getAcsResponse(request);
+		if (response.getSuccess()) {
+			return response.getData().getJobConfigInfo();
+		}
+		return null;
+	}
+
+	/**
+	 * create job.
+	 *
+	 * @param client          pop client
+	 * @param jobName         job name
+	 * @param jobProperty     job property
+	 * @param namespaceSource namespace source
+	 * @throws Exception create job exception
+	 */
+	private void createJob(DefaultAcsClient client, String jobName, JobProperty jobProperty, String namespaceSource) throws Exception {
+		CreateJobRequest request = new CreateJobRequest();
+		request.setNamespace(properties.getNamespace());
+		request.setNamespaceSource(namespaceSource);
+		request.setGroupId(properties.getGroupId());
+		request.setName(jobName);
+		request.setParameters(jobProperty.getJobParameter());
+
+		if (jobProperty.getJobType().equals(JobType.JAVA.getKey())) {
+			request.setJobType(JobType.JAVA.getKey());
+			request.setClassName(jobProperty.getClassName());
+		}
+		else {
+			request.setJobType(jobProperty.getJobType());
+		}
+
+		if (SchedulerxConstants.JOB_MODEL_MAPREDUCE_ALIAS.equals(jobProperty.getJobModel())) {
+			request.setExecuteMode(ExecuteMode.BATCH.getKey());
+		}
+		else {
+			request.setExecuteMode(jobProperty.getJobModel());
+		}
+		if (StringUtils.isNotEmpty(jobProperty.getDescription())) {
+			request.setDescription(jobProperty.getDescription());
+		}
+
+		if (StringUtils.isNotEmpty(jobProperty.getContent())) {
+			request.setContent(jobProperty.getContent());
+		}
+
+		if (StringUtils.isNotEmpty(jobProperty.getCron()) && StringUtils.isNotEmpty(jobProperty.getOneTime())) {
+			throw new IOException("cron and oneTime shouldn't set together");
+		}
+		if (StringUtils.isNotEmpty(jobProperty.getCron())) {
+			CronExpression cronExpression = new CronExpression(jobProperty.getCron());
+			Date now = new Date();
+			Date nextData = cronExpression.getTimeAfter(now);
+			Date next2Data = cronExpression.getTimeAfter(nextData);
+			if (nextData != null && next2Data != null) {
+				long interval = TimeUnit.MILLISECONDS.toSeconds((next2Data.getTime() - nextData.getTime()));
+				if (interval < SchedulerxConstants.SECOND_DELAY_MAX_VALUE) {
+					request.setTimeType(TimeType.SECOND_DELAY.getValue());
+					request.setTimeExpression(String.valueOf(interval < SchedulerxConstants.SECOND_DELAY_MIN_VALUE ?
+							SchedulerxConstants.SECOND_DELAY_MIN_VALUE : interval));
+				}
+				else {
+					request.setTimeType(TimeType.CRON.getValue());
+					request.setTimeExpression(jobProperty.getCron());
+				}
+			}
+			else {
+				request.setTimeType(TimeType.CRON.getValue());
+				request.setTimeExpression(jobProperty.getCron());
+			}
+		}
+		else if (StringUtils.isNotEmpty(jobProperty.getOneTime())) {
+			request.setTimeType(TimeType.ONE_TIME.getValue());
+			request.setTimeExpression(jobProperty.getOneTime());
+		}
+		else {
+			request.setTimeType(TimeType.API.getValue());
+		}
+
+		if (jobProperty.getTimeType() != null) {
+			request.setTimeType(jobProperty.getTimeType());
+			if (StringUtils.isNotEmpty(jobProperty.getTimeExpression())) {
+				request.setTimeExpression(jobProperty.getTimeExpression());
+			}
+		}
+
+		request.setTimeoutEnable(true);
+		request.setTimeoutKillEnable(true);
+		request.setSendChannel(SchedulerxConstants.JOB_ALARM_CHANNEL_DEFAULT);
+		request.setFailEnable(true);
+		request.setTimeout(SchedulerxConstants.JOB_TIMEOUT_DEFAULT);
+		request.setMaxAttempt(SchedulerxConstants.JOB_RETRY_COUNT_DEFAULT);
+		request.setAttemptInterval(SchedulerxConstants.JOB_RETRY_INTERVAL_DEFAULT);
+		CreateJobResponse response = client.getAcsResponse(request);
+		if (response.getSuccess()) {
+			logger.info("create schedulerx job successfully, jobId={}, jobName={}", response.getData().getJobId(), jobName);
+		}
+		else {
+			throw new IOException("create schedulerx job failed, jobName=" + jobName + ", message=" + response.getMessage());
+		}
+	}
+
+	/**
+	 * update job.
+	 *
+	 * @param client          pop client
+	 * @param jobConfigInfo   job config info
+	 * @param jobProperty     job property
+	 * @param namespaceSource namespace source
+	 * @throws Exception update job exception
+	 */
+	private void updateJob(DefaultAcsClient client, JobConfigInfo jobConfigInfo, JobProperty jobProperty, String namespaceSource) throws Exception {
+		String executeMode = jobProperty.getJobModel();
+		if (SchedulerxConstants.JOB_MODEL_MAPREDUCE_ALIAS.equals(jobProperty.getJobModel())) {
+			executeMode = ExecuteMode.BATCH.getKey();
+		}
+		int timeType;
+		String timeExpression = null;
+		if (StringUtils.isNotEmpty(jobProperty.getCron()) && StringUtils.isNotEmpty(jobProperty.getOneTime())) {
+			throw new IOException("cron and oneTime shouldn't set together");
+		}
+		if (StringUtils.isNotEmpty(jobProperty.getCron())) {
+			CronExpression cronExpression = new CronExpression(jobProperty.getCron());
+			Date now = new Date();
+			Date nextData = cronExpression.getTimeAfter(now);
+			Date next2Data = cronExpression.getTimeAfter(nextData);
+			if (nextData != null && next2Data != null) {
+				long interval = TimeUnit.MILLISECONDS.toSeconds((next2Data.getTime() - nextData.getTime()));
+				if (interval < SchedulerxConstants.SECOND_DELAY_MAX_VALUE) {
+					timeType = TimeType.SECOND_DELAY.getValue();
+					timeExpression = String.valueOf(interval < SchedulerxConstants.SECOND_DELAY_MIN_VALUE ?
+							SchedulerxConstants.SECOND_DELAY_MIN_VALUE : interval);
+				}
+				else {
+					timeType = TimeType.CRON.getValue();
+					timeExpression = jobProperty.getCron();
+				}
+			}
+			else {
+				timeType = TimeType.CRON.getValue();
+				timeExpression = jobProperty.getCron();
+			}
+		}
+		else if (StringUtils.isNotEmpty(jobProperty.getOneTime())) {
+			timeType = TimeType.ONE_TIME.getValue();
+			timeExpression = jobProperty.getOneTime();
+		}
+		else {
+			timeType = TimeType.API.getValue();
+		}
+
+		if (!jobConfigInfo.getDescription().equals(jobProperty.getDescription())
+				|| !jobConfigInfo.getClassName().equals(jobProperty.getClassName())
+				|| !jobConfigInfo.getParameters().equals(jobProperty.getJobParameter())
+				|| !jobConfigInfo.getExecuteMode().equals(executeMode)
+				|| jobConfigInfo.getTimeConfig().getTimeType() != timeType
+				|| !jobConfigInfo.getTimeConfig().getTimeExpression().equals(timeExpression)) {
+
+			UpdateJobRequest request = new UpdateJobRequest();
+			request.setNamespace(properties.getNamespace());
+			request.setNamespaceSource(namespaceSource);
+			request.setGroupId(properties.getGroupId());
+			request.setJobId(jobConfigInfo.getJobId());
+			request.setName(jobConfigInfo.getName());
+			request.setParameters(jobProperty.getJobParameter());
+
+			//java任务
+			if (jobProperty.getJobType().equals(JobType.JAVA.getKey())) {
+				request.setClassName(jobProperty.getClassName());
+			}
+			request.setExecuteMode(executeMode);
+			if (StringUtils.isNotEmpty(jobProperty.getDescription())) {
+				request.setDescription(jobProperty.getDescription());
+			}
+			request.setTimeType(timeType);
+			request.setTimeExpression(timeExpression);
+
+			request.setTimeoutEnable(true);
+			request.setTimeoutKillEnable(true);
+			request.setSendChannel(SchedulerxConstants.JOB_ALARM_CHANNEL_DEFAULT);
+			request.setFailEnable(true);
+			request.setTimeout(SchedulerxConstants.JOB_TIMEOUT_DEFAULT);
+			request.setMaxAttempt(SchedulerxConstants.JOB_RETRY_COUNT_DEFAULT);
+			request.setAttemptInterval(SchedulerxConstants.JOB_RETRY_INTERVAL_DEFAULT);
+
+			UpdateJobResponse response = client.getAcsResponse(request);
+			if (response.getSuccess()) {
+				logger.info("update schedulerx job successfully, jobId={}, jobName={}", jobConfigInfo.getJobId(), jobConfigInfo.getName());
+			}
+			else {
+				throw new IOException("update schedulerx job failed, jobName=" + jobConfigInfo.getName() + ", message=" + response.getMessage());
+			}
+		}
+	}
+
+	/**
+	 * Get namespace source.
+	 *
+	 * @return namespace source
+	 */
+	private String getNamespaceSource() {
+		if (StringUtils.isEmpty(properties.getNamespaceSource())) {
+			return SchedulerxConstants.NAMESPACE_SOURCE_SPRINGBOOT;
+		}
+		return properties.getNamespaceSource();
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/ScheduledJobSyncConfigurer.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/ScheduledJobSyncConfigurer.java
new file mode 100644
index 000000000..5f7075fdf
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/ScheduledJobSyncConfigurer.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx.service;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.cloud.scheduling.schedulerx.JobProperty;
+import com.alibaba.cloud.scheduling.schedulerx.SchedulerxProperties;
+import com.alibaba.schedulerx.common.domain.ExecuteMode;
+import com.alibaba.schedulerx.common.domain.JobType;
+import com.alibaba.schedulerx.common.domain.Pair;
+import com.alibaba.schedulerx.common.domain.TimeType;
+import com.alibaba.schedulerx.common.util.JsonUtil;
+import com.alibaba.schedulerx.common.util.StringUtils;
+import com.alibaba.schedulerx.scheduling.annotation.SchedulerX;
+import com.alibaba.schedulerx.worker.domain.SpringScheduleProfile;
+import com.alibaba.schedulerx.worker.log.LogFactory;
+import com.alibaba.schedulerx.worker.log.Logger;
+import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxSchedulingConfigurer;
+
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.CronTask;
+import org.springframework.scheduling.config.IntervalTask;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+import org.springframework.scheduling.config.Task;
+import org.springframework.scheduling.support.ScheduledMethodRunnable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Spring scheduled job sync.
+ *
+ * @author yaohui
+ * @create 2022/8/17 下午2:21
+ **/
+public class ScheduledJobSyncConfigurer implements SchedulingConfigurer {
+
+	private static final Logger logger = LogFactory.getLogger(ScheduledJobSyncConfigurer.class);
+
+	@Autowired
+	private JobSyncService jobSyncService;
+
+	@Autowired
+	private SchedulerxProperties properties;
+
+	@Autowired
+	private SchedulerxSchedulingConfigurer schedulerxSchedulingConfigurer;
+
+	@Value("${" + SchedulerxProperties.CONFIG_PREFIX + ".task-overwrite:false}")
+	private Boolean overwrite = false;
+
+	@Value("${" + SchedulerxProperties.CONFIG_PREFIX + ".task-model-default:broadcast}")
+	private String defaultModel = ExecuteMode.BROADCAST.getKey();
+
+	private boolean isValidModel(String mode) {
+		if (mode == null) {
+			return false;
+		}
+		return (ExecuteMode.BROADCAST.getKey().equals(mode) || ExecuteMode.STANDALONE.getKey().equals(mode));
+	}
+
+	private JobProperty convertToJobProperty(Task task, Object target, Method method) {
+		JobProperty jobProperty = new JobProperty();
+		Class targetClass = AopProxyUtils.ultimateTargetClass(target);
+		if (ClassUtils.isCglibProxyClass(targetClass)) {
+			targetClass = ClassUtils.getUserClass(target);
+		}
+		String jobName = targetClass.getSimpleName() + "_" + method.getName();
+		String model = this.defaultModel;
+
+		if (task != null && task instanceof CronTask) {
+			String expression = ((CronTask) task).getExpression();
+			jobProperty.setCron(expression);
+		}
+
+		if (task != null && task instanceof IntervalTask) {
+			long interval = ((IntervalTask) task).getInterval() / 1000;
+			interval = interval < 1 ? 1 : interval;
+			if (interval < 60) {
+				jobProperty.setTimeType(TimeType.SECOND_DELAY.getValue());
+			}
+			else {
+				jobProperty.setTimeType(TimeType.FIXED_RATE.getValue());
+			}
+			jobProperty.setTimeExpression(String.valueOf(interval));
+		}
+
+		SchedulerX schedulerXMethod = AnnotatedElementUtils.getMergedAnnotation(method, SchedulerX.class);
+		if (schedulerXMethod != null) {
+			if (StringUtils.isNotEmpty(schedulerXMethod.name())) {
+				jobName = schedulerXMethod.name();
+			}
+			if (isValidModel(schedulerXMethod.model())) {
+				model = schedulerXMethod.model();
+			}
+			if (StringUtils.isNotEmpty(schedulerXMethod.cron())) {
+				jobProperty.setCron(schedulerXMethod.cron());
+			}
+			if (schedulerXMethod.fixedRate() > 0) {
+				long interval = schedulerXMethod.timeUnit().toSeconds(schedulerXMethod.fixedRate());
+				interval = interval < 1 ? 1 : interval;
+				if (interval < 60) {
+					jobProperty.setTimeType(TimeType.SECOND_DELAY.getValue());
+				}
+				else {
+					jobProperty.setTimeType(TimeType.FIXED_RATE.getValue());
+				}
+				jobProperty.setTimeExpression(String.valueOf(interval));
+			}
+		}
+
+		jobProperty.setJobName(jobName);
+		jobProperty.setJobType(JobType.SPRINGSCHEDULE.getKey());
+		jobProperty.setJobModel(model);
+		SpringScheduleProfile profile = new SpringScheduleProfile();
+		profile.setClassName(targetClass.getName());
+		profile.setMethod(method.getName());
+		jobProperty.setContent(JsonUtil.toJson(profile));
+		jobProperty.setOverwrite(overwrite);
+		return jobProperty;
+	}
+
+	@Override
+	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+		logger.info("spring scheduled job is not empty, start to sync jobs...");
+		try {
+			Map<String, JobProperty> jobs = new HashMap<>();
+			if (!CollectionUtils.isEmpty(taskRegistrar.getCronTaskList())) {
+				for (CronTask cronTask : taskRegistrar.getCronTaskList()) {
+					if (cronTask.getRunnable() instanceof ScheduledMethodRunnable) {
+						ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTask.getRunnable();
+						JobProperty jobProperty = convertToJobProperty(cronTask, runnable.getTarget(), runnable.getMethod());
+						jobs.put(jobProperty.getJobName(), jobProperty);
+					}
+				}
+			}
+			if (!CollectionUtils.isEmpty(taskRegistrar.getFixedDelayTaskList())) {
+				for (IntervalTask intervalTask : taskRegistrar.getFixedDelayTaskList()) {
+					if (intervalTask.getRunnable() instanceof ScheduledMethodRunnable) {
+						ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) intervalTask.getRunnable();
+						JobProperty jobProperty = convertToJobProperty(intervalTask, runnable.getTarget(), runnable.getMethod());
+						jobs.put(jobProperty.getJobName(), jobProperty);
+					}
+				}
+			}
+			if (!CollectionUtils.isEmpty(taskRegistrar.getFixedRateTaskList())) {
+				for (IntervalTask intervalTask : taskRegistrar.getFixedRateTaskList()) {
+					if (intervalTask.getRunnable() instanceof ScheduledMethodRunnable) {
+						ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) intervalTask.getRunnable();
+						JobProperty jobProperty = convertToJobProperty(intervalTask, runnable.getTarget(), runnable.getMethod());
+						jobs.put(jobProperty.getJobName(), jobProperty);
+					}
+				}
+			}
+
+			// 获取仅SchedulerX注解任务
+			Collection<Pair<Object, Method>> schedulerXTasks = schedulerxSchedulingConfigurer.getSchedulerXTaskTargets();
+			if (schedulerXTasks != null && schedulerXTasks.size() > 0) {
+				for (Pair<Object, Method> task : schedulerXTasks) {
+					JobProperty jobProperty = convertToJobProperty(null, task.getFirst(), task.getSecond());
+					jobs.put(jobProperty.getJobName(), jobProperty);
+				}
+			}
+
+			jobSyncService.syncJobs(jobs, properties.getNamespaceSource());
+			logger.info("spring scheduled job is not empty, sync jobs finished.");
+		}
+		catch (Exception e) {
+			logger.info("spring scheduled job is not empty, sync jobs failed.", e);
+			throw new RuntimeException(e);
+		}
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java
new file mode 100644
index 000000000..bb286c847
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java
@@ -0,0 +1,1540 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx.util;
+
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import org.springframework.util.Assert;
+
+/**
+ * @author yaohui
+ */
+public final class CronExpression {
+
+	protected static final int SECOND = 0;
+	protected static final int MINUTE = 1;
+	protected static final int HOUR = 2;
+	protected static final int DAY_OF_MONTH = 3;
+	protected static final int MONTH = 4;
+	protected static final int DAY_OF_WEEK = 5;
+	protected static final int YEAR = 6;
+	protected static final int ALL_SPEC_INT = 99; // '*'
+	protected static final int NO_SPEC_INT = 98; // '?'
+	protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+	protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+	protected static final Map<String, Integer> monthMap = new HashMap<>(20);
+	protected static final Map<String, Integer> dayMap = new HashMap<>(60);
+
+	static {
+		monthMap.put("JAN", 0);
+		monthMap.put("FEB", 1);
+		monthMap.put("MAR", 2);
+		monthMap.put("APR", 3);
+		monthMap.put("MAY", 4);
+		monthMap.put("JUN", 5);
+		monthMap.put("JUL", 6);
+		monthMap.put("AUG", 7);
+		monthMap.put("SEP", 8);
+		monthMap.put("OCT", 9);
+		monthMap.put("NOV", 10);
+		monthMap.put("DEC", 11);
+
+		dayMap.put("SUN", 1);
+		dayMap.put("MON", 2);
+		dayMap.put("TUE", 3);
+		dayMap.put("WED", 4);
+		dayMap.put("THU", 5);
+		dayMap.put("FRI", 6);
+		dayMap.put("SAT", 7);
+	}
+
+	private final String cronExpression;
+	private TimeZone timeZone = null;
+	protected transient TreeSet<Integer> seconds;
+	protected transient TreeSet<Integer> minutes;
+	protected transient TreeSet<Integer> hours;
+	protected transient TreeSet<Integer> daysOfMonth;
+	protected transient TreeSet<Integer> months;
+	protected transient TreeSet<Integer> daysOfWeek;
+	protected transient TreeSet<Integer> years;
+
+	protected transient boolean lastdayOfWeek = false;
+	protected transient int nthdayOfWeek = 0;
+	protected transient boolean lastdayOfMonth = false;
+	protected transient boolean nearestWeekday = false;
+	protected transient int lastdayOffset = 0;
+	protected transient boolean expressionParsed = false;
+
+	/**
+	 * MAX_YEAR.
+	 */
+	public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+	/**
+	 * MIN_CAL.
+	 */
+	public static final Calendar MIN_CAL = Calendar.getInstance();
+
+	static {
+		MIN_CAL.set(1970, 0, 1);
+	}
+
+	/**
+	 * MIN_DATE.
+	 */
+	public static final Date MIN_DATE = MIN_CAL.getTime();
+
+	/**
+	 * Constructs a new <CODE>CronExpression</CODE> based on the specified
+	 * parameter.
+	 *
+	 * @param cronExpression String representation of the cron expression the
+	 *                       new object should represent
+	 * @throws ParseException if the string expression cannot be parsed into a valid
+	 *                        <CODE>CronExpression</CODE>
+	 */
+	public CronExpression(final String cronExpression) throws ParseException {
+		if (cronExpression == null) {
+			throw new IllegalArgumentException("cronExpression cannot be null");
+		}
+
+		this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+		buildExpression(this.cronExpression);
+	}
+
+	/**
+	 * Indicates whether the given date satisfies the cron expression. Note that
+	 * milliseconds are ignored, so two Dates falling on different milliseconds
+	 * of the same second will always have the same result here.
+	 *
+	 * @param date the date to evaluate
+	 * @return a boolean indicating whether the given date satisfies the cron
+	 * expression.
+	 */
+	public boolean isSatisfiedBy(final Date date) {
+		final Calendar testDateCal = Calendar.getInstance(getTimeZone());
+		testDateCal.setTime(date);
+		testDateCal.set(Calendar.MILLISECOND, 0);
+		final Date originalDate = testDateCal.getTime();
+
+		testDateCal.add(Calendar.SECOND, -1);
+
+		final Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+		return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+	}
+
+	/**
+	 * Returns the next date/time <I>after</I> the given date/time which
+	 * satisfies the cron expression.
+	 *
+	 * @param date the date/time at which to begin the search for the next valid
+	 *             date/time
+	 * @return the next valid date/time.
+	 */
+	public Date getNextValidTimeAfter(final Date date) {
+		return getTimeAfter(date);
+	}
+
+	/**
+	 * Returns the next date/time <I>after</I> the given date/time which does
+	 * <I>not</I> satisfy the expression.
+	 *
+	 * @param date the date/time at which to begin the search for the next
+	 *             invalid date/time
+	 * @return the next valid date/time.
+	 */
+	public Date getNextInvalidTimeAfter(final Date date) {
+		long difference = 1000;
+
+		//move back to the nearest second so differences will be accurate
+		final Calendar adjustCal = Calendar.getInstance(getTimeZone());
+		adjustCal.setTime(date);
+		adjustCal.set(Calendar.MILLISECOND, 0);
+		Date lastDate = adjustCal.getTime();
+
+		Date newDate;
+
+		//FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+		//keep getting the next included time until it's farther than one second
+		// apart. At that point, lastDate is the last valid fire time. We return
+		// the second immediately following it.
+		while (difference == 1000) {
+			newDate = getTimeAfter(lastDate);
+			if (newDate == null) {
+				break;
+			}
+
+			difference = newDate.getTime() - lastDate.getTime();
+
+			if (difference == 1000) {
+				lastDate = newDate;
+			}
+		}
+
+		return new Date(lastDate.getTime() + 1000);
+	}
+
+	/**
+	 * Returns the time zone for which this <code>CronExpression</code>
+	 * will be resolved.
+	 *
+	 * @return the time zone.
+	 */
+	public TimeZone getTimeZone() {
+		if (timeZone == null) {
+			timeZone = TimeZone.getDefault();
+		}
+
+		return timeZone;
+	}
+
+	public void setTimeZone(final TimeZone timeZone) {
+		this.timeZone = timeZone;
+	}
+
+	@Override
+	public String toString() {
+		return cronExpression;
+	}
+
+	/**
+	 * check cron expression is valid or not.
+	 *
+	 * @param cronExpression cron expression
+	 * @return return{@code true}if Cron expression is valid,otherwise return{@code false}.
+	 */
+	public static boolean isValidExpression(final String cronExpression) {
+		try {
+			new CronExpression(cronExpression);
+		}
+		catch (final ParseException pe) {
+			return false;
+		}
+		return true;
+	}
+
+	public static void validateExpression(final String cronExpression) throws ParseException {
+		new CronExpression(cronExpression);
+	}
+
+	protected void buildExpression(final String expression) throws ParseException {
+		expressionParsed = true;
+		try {
+			if (seconds == null) {
+				seconds = new TreeSet<>();
+			}
+			if (minutes == null) {
+				minutes = new TreeSet<>();
+			}
+			if (hours == null) {
+				hours = new TreeSet<>();
+			}
+			if (daysOfMonth == null) {
+				daysOfMonth = new TreeSet<>();
+			}
+			if (months == null) {
+				months = new TreeSet<>();
+			}
+			if (daysOfWeek == null) {
+				daysOfWeek = new TreeSet<>();
+			}
+			if (years == null) {
+				years = new TreeSet<>();
+			}
+
+			int exprOn = SECOND;
+
+			final StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+					false);
+
+			while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+				final String expr = exprsTok.nextToken().trim();
+
+				// throw an exception if L is used with other days of the month
+				if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+					throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+				}
+				// throw an exception if L is used with other days of the week
+				if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+					throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+				}
+				if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) {
+					throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+				}
+
+				final StringTokenizer vTok = new StringTokenizer(expr, ",");
+				while (vTok.hasMoreTokens()) {
+					final String v = vTok.nextToken();
+					storeExpressionVals(0, v, exprOn);
+				}
+
+				exprOn++;
+			}
+
+			if (exprOn <= DAY_OF_WEEK) {
+				throw new ParseException("Unexpected end of expression.",
+						expression.length());
+			}
+
+			if (exprOn <= YEAR) {
+				storeExpressionVals(0, "*", YEAR);
+			}
+
+			final TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
+			final TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
+
+			// Copying the logic from the UnsupportedOperationException below
+			final boolean dayOfMSpec = !dom.contains(NO_SPEC);
+			final boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+			if (!dayOfMSpec || dayOfWSpec) {
+				if (!dayOfWSpec || dayOfMSpec) {
+					throw new ParseException(
+							"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+				}
+			}
+		}
+		catch (final ParseException pe) {
+			throw pe;
+		}
+		catch (final Exception e) {
+			throw new ParseException("Illegal cron expression format ("
+					+ e.toString() + ")", 0);
+		}
+	}
+
+	protected int storeExpressionVals(final int pos, final String s, final int type)
+			throws ParseException {
+
+		int incr = 0;
+		int i = skipWhiteSpace(pos, s);
+		if (i >= s.length()) {
+			return i;
+		}
+		char c = s.charAt(i);
+		if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+			String sub = s.substring(i, i + 3);
+			int sval = -1;
+			int eval = -1;
+			if (type == MONTH) {
+				sval = getMonthNumber(sub) + 1;
+				if (sval <= 0) {
+					throw new ParseException("Invalid Month value: '" + sub + "'", i);
+				}
+				if (s.length() > i + 3 && (s.charAt(i + 3) == '-')) {
+					i += 4;
+					sub = s.substring(i, i + 3);
+					eval = getMonthNumber(sub) + 1;
+					Assert.isTrue(eval > 0, "Invalid Month value: '" + sub + "'");
+				}
+			}
+			else if (type == DAY_OF_WEEK) {
+				sval = getDayOfWeekNumber(sub);
+				if (sval < 0) {
+					throw new ParseException("Invalid Day-of-Week value: '"
+							+ sub + "'", i);
+				}
+				if (s.length() > i + 3) {
+					c = s.charAt(i + 3);
+					if (c == '-') {
+						i += 4;
+						sub = s.substring(i, i + 3);
+						eval = getDayOfWeekNumber(sub);
+						Assert.isTrue(eval >= 0, "Invalid Day-of-Week value: '" + sub + "'");
+					}
+					else if (c == '#') {
+						i += 4;
+						nthdayOfWeek = Integer.parseInt(s.substring(i));
+						Assert.isTrue(nthdayOfWeek > 0 && nthdayOfWeek < 6, "A numeric value between 1 and 5 must follow the '#' option");
+					}
+					else if (c == 'L') {
+						lastdayOfWeek = true;
+						i++;
+					}
+				}
+			}
+			else {
+				throw new ParseException(
+						"Illegal characters for this position: '" + sub + "'",
+						i);
+			}
+			if (eval != -1) {
+				incr = 1;
+			}
+			addToSet(sval, eval, incr, type);
+			return (i + 3);
+		}
+
+		if (c == '?') {
+			i++;
+			if ((i + 1) < s.length()
+					&& (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+				throw new ParseException("Illegal character after '?': "
+						+ s.charAt(i), i);
+			}
+			if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+				throw new ParseException(
+						"'?' can only be specfied for Day-of-Month or Day-of-Week.",
+						i);
+			}
+			if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+				final int val = daysOfMonth.last();
+				if (val == NO_SPEC_INT) {
+					throw new ParseException(
+							"'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
+							i);
+				}
+			}
+
+			addToSet(NO_SPEC_INT, -1, 0, type);
+			return i;
+		}
+
+		if (c == '*' || c == '/') {
+			if (c == '*' && (i + 1) >= s.length()) {
+				addToSet(ALL_SPEC_INT, -1, incr, type);
+				return i + 1;
+			}
+			else if (c == '/'
+					&& ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+					.charAt(i + 1) == '\t')) {
+				throw new ParseException("'/' must be followed by an integer.", i);
+			}
+			else if (c == '*') {
+				i++;
+			}
+			c = s.charAt(i);
+			if (c == '/') { // is an increment specified?
+				i++;
+				if (i >= s.length()) {
+					throw new ParseException("Unexpected end of string.", i);
+				}
+
+				incr = getNumericValue(s, i);
+
+				i++;
+				if (incr > 10) {
+					i++;
+				}
+				if (incr > 59 && (type == SECOND || type == MINUTE)) {
+					throw new ParseException("Increment > 60 : " + incr, i);
+				}
+				else if (incr > 23 && (type == HOUR)) {
+					throw new ParseException("Increment > 24 : " + incr, i);
+				}
+				else if (incr > 31 && (type == DAY_OF_MONTH)) {
+					throw new ParseException("Increment > 31 : " + incr, i);
+				}
+				else if (incr > 7 && (type == DAY_OF_WEEK)) {
+					throw new ParseException("Increment > 7 : " + incr, i);
+				}
+				else if (incr > 12 && (type == MONTH)) {
+					throw new ParseException("Increment > 12 : " + incr, i);
+				}
+			}
+			else {
+				incr = 1;
+			}
+
+			addToSet(ALL_SPEC_INT, -1, incr, type);
+			return i;
+		}
+		else if (c == 'L') {
+			i++;
+			if (type == DAY_OF_MONTH) {
+				lastdayOfMonth = true;
+			}
+			if (type == DAY_OF_WEEK) {
+				addToSet(7, 7, 0, type);
+			}
+			if (type == DAY_OF_MONTH && s.length() > i) {
+				c = s.charAt(i);
+				if (c == '-') {
+					final ValueSet vs = getValue(0, s, i + 1);
+					lastdayOffset = vs.value;
+					if (lastdayOffset > 30) {
+						throw new ParseException("Offset from last day must be <= 30", i + 1);
+					}
+					i = vs.pos;
+				}
+				if (s.length() > i) {
+					c = s.charAt(i);
+					if (c == 'W') {
+						nearestWeekday = true;
+						i++;
+					}
+				}
+			}
+			return i;
+		}
+		else if (c >= '0' && c <= '9') {
+			int val = Integer.parseInt(String.valueOf(c));
+			i++;
+			if (i >= s.length()) {
+				addToSet(val, -1, -1, type);
+			}
+			else {
+				c = s.charAt(i);
+				if (c >= '0' && c <= '9') {
+					final ValueSet vs = getValue(val, s, i);
+					val = vs.value;
+					i = vs.pos;
+				}
+				i = checkNext(i, s, val, type);
+				return i;
+			}
+		}
+		else {
+			throw new ParseException("Unexpected character: " + c, i);
+		}
+
+		return i;
+	}
+
+	protected int checkNext(final int pos, final String s, final int val, final int type)
+			throws ParseException {
+
+		int end = -1;
+		int i = pos;
+
+		if (i >= s.length()) {
+			addToSet(val, end, -1, type);
+			return i;
+		}
+
+		char c = s.charAt(pos);
+
+		if (c == 'L') {
+			if (type == DAY_OF_WEEK) {
+				if (val < 1 || val > 7) {
+					throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+				}
+				lastdayOfWeek = true;
+			}
+			else {
+				throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+			}
+			final TreeSet<Integer> set = getSet(type);
+			set.add(val);
+			i++;
+			return i;
+		}
+
+		if (c == 'W') {
+			if (type == DAY_OF_MONTH) {
+				nearestWeekday = true;
+			}
+			else {
+				throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+			}
+			if (val > 31) {
+				throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+			}
+			final TreeSet<Integer> set = getSet(type);
+			set.add(val);
+			i++;
+			return i;
+		}
+
+		if (c == '#') {
+			if (type != DAY_OF_WEEK) {
+				throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+			}
+			i++;
+			try {
+				nthdayOfWeek = Integer.parseInt(s.substring(i));
+				if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+					throw new Exception();
+				}
+			}
+			catch (final Exception e) {
+				throw new ParseException(
+						"A numeric value between 1 and 5 must follow the '#' option",
+						i);
+			}
+
+			final TreeSet<Integer> set = getSet(type);
+			set.add(val);
+			i++;
+			return i;
+		}
+
+		if (c == '-') {
+			i++;
+			c = s.charAt(i);
+			final int v = Integer.parseInt(String.valueOf(c));
+			end = v;
+			i++;
+			if (i >= s.length()) {
+				addToSet(val, end, 1, type);
+				return i;
+			}
+			c = s.charAt(i);
+			if (c >= '0' && c <= '9') {
+				final ValueSet vs = getValue(v, s, i);
+				end = vs.value;
+				i = vs.pos;
+			}
+			if (i < s.length() && (s.charAt(i) == '/')) {
+				i++;
+				c = s.charAt(i);
+				final int v2 = Integer.parseInt(String.valueOf(c));
+				i++;
+				if (i >= s.length()) {
+					addToSet(val, end, v2, type);
+					return i;
+				}
+				c = s.charAt(i);
+				if (c >= '0' && c <= '9') {
+					final ValueSet vs = getValue(v2, s, i);
+					final int v3 = vs.value;
+					addToSet(val, end, v3, type);
+					i = vs.pos;
+					return i;
+				}
+				else {
+					addToSet(val, end, v2, type);
+					return i;
+				}
+			}
+			else {
+				addToSet(val, end, 1, type);
+				return i;
+			}
+		}
+
+		if (c == '/') {
+			i++;
+			c = s.charAt(i);
+			final int v2 = Integer.parseInt(String.valueOf(c));
+			i++;
+			if (i >= s.length()) {
+				addToSet(val, end, v2, type);
+				return i;
+			}
+			c = s.charAt(i);
+			if (c >= '0' && c <= '9') {
+				final ValueSet vs = getValue(v2, s, i);
+				final int v3 = vs.value;
+				addToSet(val, end, v3, type);
+				i = vs.pos;
+				return i;
+			}
+			else {
+				throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+			}
+		}
+
+		addToSet(val, end, 0, type);
+		i++;
+		return i;
+	}
+
+	public String getCronExpression() {
+		return cronExpression;
+	}
+
+	public String getExpressionSummary() {
+		final StringBuilder buf = new StringBuilder();
+
+		buf.append("seconds: ");
+		buf.append(getExpressionSetSummary(seconds));
+		buf.append("\n");
+		buf.append("minutes: ");
+		buf.append(getExpressionSetSummary(minutes));
+		buf.append("\n");
+		buf.append("hours: ");
+		buf.append(getExpressionSetSummary(hours));
+		buf.append("\n");
+		buf.append("daysOfMonth: ");
+		buf.append(getExpressionSetSummary(daysOfMonth));
+		buf.append("\n");
+		buf.append("months: ");
+		buf.append(getExpressionSetSummary(months));
+		buf.append("\n");
+		buf.append("daysOfWeek: ");
+		buf.append(getExpressionSetSummary(daysOfWeek));
+		buf.append("\n");
+		buf.append("lastdayOfWeek: ");
+		buf.append(lastdayOfWeek);
+		buf.append("\n");
+		buf.append("nearestWeekday: ");
+		buf.append(nearestWeekday);
+		buf.append("\n");
+		buf.append("NthDayOfWeek: ");
+		buf.append(nthdayOfWeek);
+		buf.append("\n");
+		buf.append("lastdayOfMonth: ");
+		buf.append(lastdayOfMonth);
+		buf.append("\n");
+		buf.append("years: ");
+		buf.append(getExpressionSetSummary(years));
+		buf.append("\n");
+
+		return buf.toString();
+	}
+
+	protected String getExpressionSetSummary(final java.util.Set<Integer> set) {
+
+		if (set.contains(NO_SPEC)) {
+			return "?";
+		}
+		if (set.contains(ALL_SPEC)) {
+			return "*";
+		}
+
+		final StringBuilder buf = new StringBuilder();
+
+		final Iterator<Integer> itr = set.iterator();
+		boolean first = true;
+		while (itr.hasNext()) {
+			final Integer iVal = itr.next();
+			final String val = iVal.toString();
+			if (!first) {
+				buf.append(",");
+			}
+			buf.append(val);
+			first = false;
+		}
+
+		return buf.toString();
+	}
+
+	protected String getExpressionSetSummary(final java.util.ArrayList<Integer> list) {
+
+		if (list.contains(NO_SPEC)) {
+			return "?";
+		}
+		if (list.contains(ALL_SPEC)) {
+			return "*";
+		}
+
+		final StringBuilder buf = new StringBuilder();
+
+		final Iterator<Integer> itr = list.iterator();
+		boolean first = true;
+		while (itr.hasNext()) {
+			final Integer iVal = itr.next();
+			final String val = iVal.toString();
+			if (!first) {
+				buf.append(",");
+			}
+			buf.append(val);
+			first = false;
+		}
+
+		return buf.toString();
+	}
+
+	protected int skipWhiteSpace(int i, final String s) {
+		for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+			// empty
+		}
+
+		return i;
+	}
+
+	protected int findNextWhiteSpace(int i, final String s) {
+		for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+			// empty
+		}
+
+		return i;
+	}
+
+	protected void addToSet(final int val, final int end, int incr, final int type)
+			throws ParseException {
+
+		final TreeSet<Integer> set = getSet(type);
+
+		if (type == SECOND || type == MINUTE) {
+			if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+				throw new ParseException(
+						"Minute and Second values must be between 0 and 59",
+						-1);
+			}
+		}
+		else if (type == HOUR) {
+			if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+				throw new ParseException(
+						"Hour values must be between 0 and 23", -1);
+			}
+		}
+		else if (type == DAY_OF_MONTH) {
+			if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+					&& (val != NO_SPEC_INT)) {
+				throw new ParseException(
+						"Day of month values must be between 1 and 31", -1);
+			}
+		}
+		else if (type == MONTH) {
+			if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+				throw new ParseException(
+						"Month values must be between 1 and 12", -1);
+			}
+		}
+		else if (type == DAY_OF_WEEK) {
+			if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+					&& (val != NO_SPEC_INT)) {
+				throw new ParseException(
+						"Day-of-Week values must be between 1 and 7", -1);
+			}
+		}
+
+		if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+			if (val != -1) {
+				set.add(val);
+			}
+			else {
+				set.add(NO_SPEC);
+			}
+
+			return;
+		}
+
+		int startAt = val;
+		int stopAt = end;
+
+		if (val == ALL_SPEC_INT && incr <= 0) {
+			incr = 1;
+			set.add(ALL_SPEC); // put in a marker, but also fill values
+		}
+
+		if (type == SECOND || type == MINUTE) {
+			if (stopAt == -1) {
+				stopAt = 59;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 0;
+			}
+		}
+		else if (type == HOUR) {
+			if (stopAt == -1) {
+				stopAt = 23;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 0;
+			}
+		}
+		else if (type == DAY_OF_MONTH) {
+			if (stopAt == -1) {
+				stopAt = 31;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 1;
+			}
+		}
+		else if (type == MONTH) {
+			if (stopAt == -1) {
+				stopAt = 12;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 1;
+			}
+		}
+		else if (type == DAY_OF_WEEK) {
+			if (stopAt == -1) {
+				stopAt = 7;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 1;
+			}
+		}
+		else if (type == YEAR) {
+			if (stopAt == -1) {
+				stopAt = MAX_YEAR;
+			}
+			if (startAt == -1 || startAt == ALL_SPEC_INT) {
+				startAt = 1970;
+			}
+		}
+
+		// if the end of the range is before the start, then we need to overflow into
+		// the next day, month etc. This is done by adding the maximum amount for that
+		// type, and using modulus max to determine the value being added.
+		int max = -1;
+		if (stopAt < startAt) {
+			switch (type) {
+				case SECOND:
+					max = 60;
+					break;
+				case MINUTE:
+					max = 60;
+					break;
+				case HOUR:
+					max = 24;
+					break;
+				case MONTH:
+					max = 12;
+					break;
+				case DAY_OF_WEEK:
+					max = 7;
+					break;
+				case DAY_OF_MONTH:
+					max = 31;
+					break;
+				case YEAR:
+					throw new IllegalArgumentException("Start year must be less than stop year");
+				default:
+					throw new IllegalArgumentException("Unexpected type encountered");
+			}
+			stopAt += max;
+		}
+
+		for (int i = startAt; i <= stopAt; i += incr) {
+			if (max == -1) {
+				// ie: there's no max to overflow over
+				set.add(i);
+			}
+			else {
+				// take the modulus to get the real value
+				int i2 = i % max;
+
+				// 1-indexed ranges should not include 0, and should include their max
+				if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) {
+					i2 = max;
+				}
+
+				set.add(i2);
+			}
+		}
+	}
+
+	TreeSet<Integer> getSet(final int type) throws ParseException {
+		switch (type) {
+			case SECOND:
+				return seconds;
+			case MINUTE:
+				return minutes;
+			case HOUR:
+				return hours;
+			case DAY_OF_MONTH:
+				return daysOfMonth;
+			case MONTH:
+				return months;
+			case DAY_OF_WEEK:
+				return daysOfWeek;
+			case YEAR:
+				return years;
+			default:
+				throw new ParseException("Invalid type value: '" + type + "'", -1);
+		}
+	}
+
+	protected ValueSet getValue(final int v, final String s, int i) {
+		char c = s.charAt(i);
+		final StringBuilder s1 = new StringBuilder(String.valueOf(v));
+		while (c >= '0' && c <= '9') {
+			s1.append(c);
+			i++;
+			if (i >= s.length()) {
+				break;
+			}
+			c = s.charAt(i);
+		}
+		final ValueSet val = new ValueSet();
+
+		val.pos = (i < s.length()) ? i : i + 1;
+		val.value = Integer.parseInt(s1.toString());
+		return val;
+	}
+
+	protected int getNumericValue(final String s, final int i) {
+		final int endOfVal = findNextWhiteSpace(i, s);
+		final String val = s.substring(i, endOfVal);
+		return Integer.parseInt(val);
+	}
+
+	protected int getMonthNumber(final String s) {
+		final Integer integer = monthMap.get(s);
+
+		if (integer == null) {
+			return -1;
+		}
+
+		return integer;
+	}
+
+	protected int getDayOfWeekNumber(final String s) {
+		final Integer integer = dayMap.get(s);
+
+		if (integer == null) {
+			return -1;
+		}
+
+		return integer;
+	}
+
+	public Date getTimeAfter(Date afterTime) {
+
+		// Computation is based on Gregorian year only.
+		final Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+		// move ahead one second, since we're computing the time *after* the
+		// given time
+		if (afterTime == null) {
+			return null;
+		}
+		afterTime = new Date(afterTime.getTime() + 1000);
+		// CronTrigger does not deal with milliseconds
+		cl.setTime(afterTime);
+		cl.set(Calendar.MILLISECOND, 0);
+
+		boolean gotOne = false;
+		// loop until we've computed the next time, or we've past the endTime
+		while (!gotOne) {
+			//if (endTime != null && cl.getTime().after(endTime)) return null;
+			if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+				return null;
+			}
+
+			SortedSet<Integer> st = null;
+			int t = 0;
+
+			int sec = cl.get(Calendar.SECOND);
+			int min = cl.get(Calendar.MINUTE);
+
+			// get second.................................................
+			st = seconds.tailSet(sec);
+			if (st != null && st.size() != 0) {
+				sec = st.first();
+			}
+			else {
+				sec = seconds.first();
+				min++;
+				cl.set(Calendar.MINUTE, min);
+			}
+			cl.set(Calendar.SECOND, sec);
+
+			min = cl.get(Calendar.MINUTE);
+			int hr = cl.get(Calendar.HOUR_OF_DAY);
+			t = -1;
+
+			// get minute.................................................
+			st = minutes.tailSet(min);
+			if (st != null && st.size() != 0) {
+				t = min;
+				min = st.first();
+			}
+			else {
+				min = minutes.first();
+				hr++;
+			}
+			if (min != t) {
+				cl.set(Calendar.SECOND, 0);
+				cl.set(Calendar.MINUTE, min);
+				setCalendarHour(cl, hr);
+				continue;
+			}
+			cl.set(Calendar.MINUTE, min);
+
+			hr = cl.get(Calendar.HOUR_OF_DAY);
+			int day = cl.get(Calendar.DAY_OF_MONTH);
+			t = -1;
+
+			// get hour...................................................
+			st = hours.tailSet(hr);
+			if (st != null && st.size() != 0) {
+				t = hr;
+				hr = st.first();
+			}
+			else {
+				hr = hours.first();
+				day++;
+			}
+			if (hr != t) {
+				cl.set(Calendar.SECOND, 0);
+				cl.set(Calendar.MINUTE, 0);
+				cl.set(Calendar.DAY_OF_MONTH, day);
+				setCalendarHour(cl, hr);
+				continue;
+			}
+			cl.set(Calendar.HOUR_OF_DAY, hr);
+
+			day = cl.get(Calendar.DAY_OF_MONTH);
+			int mon = cl.get(Calendar.MONTH) + 1;
+			// '+ 1' because calendar is 0-based for this field, and we are
+			// 1-based
+			t = -1;
+			int tmon = mon;
+
+			// get day...................................................
+			final boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+			final boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+			if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+				st = daysOfMonth.tailSet(day);
+				if (lastdayOfMonth) {
+					if (!nearestWeekday) {
+						t = day;
+						day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+						day -= lastdayOffset;
+						day = t > day ? 1 : day;
+						if (t > day && ++mon > 12) {
+							mon = 1;
+							tmon = 3333; // ensure test of mon != tmon further below fails
+							cl.add(Calendar.YEAR, 1);
+						}
+					}
+					else {
+						t = day;
+						day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+						day -= lastdayOffset;
+
+						final Calendar tcal = Calendar.getInstance(getTimeZone());
+						tcal.set(Calendar.SECOND, 0);
+						tcal.set(Calendar.MINUTE, 0);
+						tcal.set(Calendar.HOUR_OF_DAY, 0);
+						tcal.set(Calendar.DAY_OF_MONTH, day);
+						tcal.set(Calendar.MONTH, mon - 1);
+						tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+						final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+						final int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+						if (dow == Calendar.SATURDAY && day == 1) {
+							day += 2;
+						}
+						else if (dow == Calendar.SATURDAY) {
+							day -= 1;
+						}
+						else if (dow == Calendar.SUNDAY && day == ldom) {
+							day -= 2;
+						}
+						else if (dow == Calendar.SUNDAY) {
+							day += 1;
+						}
+
+						tcal.set(Calendar.SECOND, sec);
+						tcal.set(Calendar.MINUTE, min);
+						tcal.set(Calendar.HOUR_OF_DAY, hr);
+						tcal.set(Calendar.DAY_OF_MONTH, day);
+						tcal.set(Calendar.MONTH, mon - 1);
+						final Date nTime = tcal.getTime();
+						if (nTime.before(afterTime)) {
+							day = 1;
+							mon++;
+						}
+					}
+				}
+				else if (nearestWeekday) {
+					t = day;
+					day = daysOfMonth.first();
+
+					final Calendar tcal = Calendar.getInstance(getTimeZone());
+					tcal.set(Calendar.SECOND, 0);
+					tcal.set(Calendar.MINUTE, 0);
+					tcal.set(Calendar.HOUR_OF_DAY, 0);
+					tcal.set(Calendar.DAY_OF_MONTH, day);
+					tcal.set(Calendar.MONTH, mon - 1);
+					tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+					final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+					final int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+					if (dow == Calendar.SATURDAY && day == 1) {
+						day += 2;
+					}
+					else if (dow == Calendar.SATURDAY) {
+						day -= 1;
+					}
+					else if (dow == Calendar.SUNDAY && day == ldom) {
+						day -= 2;
+					}
+					else if (dow == Calendar.SUNDAY) {
+						day += 1;
+					}
+
+					tcal.set(Calendar.SECOND, sec);
+					tcal.set(Calendar.MINUTE, min);
+					tcal.set(Calendar.HOUR_OF_DAY, hr);
+					tcal.set(Calendar.DAY_OF_MONTH, day);
+					tcal.set(Calendar.MONTH, mon - 1);
+					final Date nTime = tcal.getTime();
+					if (nTime.before(afterTime)) {
+						day = daysOfMonth.first();
+						mon++;
+					}
+				}
+				else if (st != null && st.size() != 0) {
+					t = day;
+					day = st.first();
+					// make sure we don't over-run a short month, such as february
+					final int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+					if (day > lastDay) {
+						day = daysOfMonth.first();
+						mon++;
+					}
+				}
+				else {
+					day = daysOfMonth.first();
+					mon++;
+				}
+
+				if (day != t || mon != tmon) {
+					cl.set(Calendar.SECOND, 0);
+					cl.set(Calendar.MINUTE, 0);
+					cl.set(Calendar.HOUR_OF_DAY, 0);
+					cl.set(Calendar.DAY_OF_MONTH, day);
+					cl.set(Calendar.MONTH, mon - 1);
+					// '- 1' because calendar is 0-based for this field, and we
+					// are 1-based
+					continue;
+				}
+			}
+			else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+				if (lastdayOfWeek) { // are we looking for the last XXX day of
+					// the month?
+					final int dow = daysOfWeek.first(); // desired
+					// d-o-w
+					final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+					int daysToAdd = 0;
+					if (cDow < dow) {
+						daysToAdd = dow - cDow;
+					}
+					if (cDow > dow) {
+						daysToAdd = dow + (7 - cDow);
+					}
+
+					final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+					if (day + daysToAdd > lDay) { // did we already miss the
+						// last one?
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, 1);
+						cl.set(Calendar.MONTH, mon);
+						// no '- 1' here because we are promoting the month
+						continue;
+					}
+
+					// find date of last occurrence of this day in this month...
+					while ((day + daysToAdd + 7) <= lDay) {
+						daysToAdd += 7;
+					}
+					day += daysToAdd;
+					if (daysToAdd > 0) {
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, day);
+						cl.set(Calendar.MONTH, mon - 1);
+						// '- 1' here because we are not promoting the month
+						continue;
+					}
+				}
+				else if (nthdayOfWeek != 0) {
+					// are we looking for the Nth XXX day in the month?
+					final int dow = daysOfWeek.first(); // desired
+					// d-o-w
+					final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+					int daysToAdd = 0;
+					if (cDow < dow) {
+						daysToAdd = dow - cDow;
+					}
+					else if (cDow > dow) {
+						daysToAdd = dow + (7 - cDow);
+					}
+
+					boolean dayShifted = false;
+					if (daysToAdd > 0) {
+						dayShifted = true;
+					}
+
+					day += daysToAdd;
+					int weekOfMonth = day / 7;
+					if (day % 7 > 0) {
+						weekOfMonth++;
+					}
+
+					daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+					day += daysToAdd;
+					if (daysToAdd < 0
+							|| day > getLastDayOfMonth(mon, cl
+							.get(Calendar.YEAR))) {
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, 1);
+						cl.set(Calendar.MONTH, mon);
+						// no '- 1' here because we are promoting the month
+						continue;
+					}
+					else if (daysToAdd > 0 || dayShifted) {
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, day);
+						cl.set(Calendar.MONTH, mon - 1);
+						// '- 1' here because we are NOT promoting the month
+						continue;
+					}
+				}
+				else {
+					final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+					int dow = daysOfWeek.first(); // desired
+					// d-o-w
+					st = daysOfWeek.tailSet(cDow);
+					if (st != null && st.size() > 0) {
+						dow = st.first();
+					}
+
+					int daysToAdd = 0;
+					if (cDow < dow) {
+						daysToAdd = dow - cDow;
+					}
+					if (cDow > dow) {
+						daysToAdd = dow + (7 - cDow);
+					}
+
+					final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+					if (day + daysToAdd > lDay) { // will we pass the end of
+						// the month?
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, 1);
+						cl.set(Calendar.MONTH, mon);
+						// no '- 1' here because we are promoting the month
+						continue;
+					}
+					else if (daysToAdd > 0) { // are we switching days?
+						cl.set(Calendar.SECOND, 0);
+						cl.set(Calendar.MINUTE, 0);
+						cl.set(Calendar.HOUR_OF_DAY, 0);
+						cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+						cl.set(Calendar.MONTH, mon - 1);
+						// '- 1' because calendar is 0-based for this field,
+						// and we are 1-based
+						continue;
+					}
+				}
+			}
+			else { // dayOfWSpec && !dayOfMSpec
+				throw new UnsupportedOperationException(
+						"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+			}
+			cl.set(Calendar.DAY_OF_MONTH, day);
+
+			mon = cl.get(Calendar.MONTH) + 1;
+			// '+ 1' because calendar is 0-based for this field, and we are
+			// 1-based
+			int year = cl.get(Calendar.YEAR);
+			t = -1;
+
+			// test for expressions that never generate a valid fire date,
+			// but keep looping...
+			if (year > MAX_YEAR) {
+				return null;
+			}
+
+			// get month...................................................
+			st = months.tailSet(mon);
+			if (st != null && st.size() != 0) {
+				t = mon;
+				mon = st.first();
+			}
+			else {
+				mon = months.first();
+				year++;
+			}
+			if (mon != t) {
+				cl.set(Calendar.SECOND, 0);
+				cl.set(Calendar.MINUTE, 0);
+				cl.set(Calendar.HOUR_OF_DAY, 0);
+				cl.set(Calendar.DAY_OF_MONTH, 1);
+				cl.set(Calendar.MONTH, mon - 1);
+				// '- 1' because calendar is 0-based for this field, and we are
+				// 1-based
+				cl.set(Calendar.YEAR, year);
+				continue;
+			}
+			cl.set(Calendar.MONTH, mon - 1);
+			// '- 1' because calendar is 0-based for this field, and we are
+			// 1-based
+
+			year = cl.get(Calendar.YEAR);
+			t = -1;
+
+			// get year...................................................
+			st = years.tailSet(year);
+			if (st != null && st.size() != 0) {
+				t = year;
+				year = st.first();
+			}
+			else {
+				return null; // ran out of years...
+			}
+
+			if (year != t) {
+				cl.set(Calendar.SECOND, 0);
+				cl.set(Calendar.MINUTE, 0);
+				cl.set(Calendar.HOUR_OF_DAY, 0);
+				cl.set(Calendar.DAY_OF_MONTH, 1);
+				cl.set(Calendar.MONTH, 0);
+				// '- 1' because calendar is 0-based for this field, and we are
+				// 1-based
+				cl.set(Calendar.YEAR, year);
+				continue;
+			}
+			cl.set(Calendar.YEAR, year);
+
+			gotOne = true;
+		} // while( !done )
+
+		return cl.getTime();
+	}
+
+	/**
+	 * Advance the calendar to the particular hour paying particular attention
+	 * to daylight saving problems.
+	 *
+	 * @param cal  the calendar to operate on
+	 * @param hour the hour to set
+	 */
+	protected void setCalendarHour(final Calendar cal, final int hour) {
+		cal.set(Calendar.HOUR_OF_DAY, hour);
+		if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+			cal.set(Calendar.HOUR_OF_DAY, hour + 1);
+		}
+	}
+
+	protected Date getTimeBefore(final Date targetDate) {
+		final Calendar cl = Calendar.getInstance(getTimeZone());
+
+		// CronTrigger does not deal with milliseconds, so truncate target
+		cl.setTime(targetDate);
+		cl.set(Calendar.MILLISECOND, 0);
+		final Date targetDateNoMs = cl.getTime();
+
+		// to match this
+		Date start = targetDateNoMs;
+		final long minIncrement = findMinIncrement();
+		Date prevFireTime;
+		do {
+			final Date prevCheckDate = new Date(start.getTime() - minIncrement);
+			prevFireTime = getTimeAfter(prevCheckDate);
+			if (prevFireTime == null || prevFireTime.before(MIN_DATE)) {
+				return null;
+			}
+			start = prevCheckDate;
+		} while (prevFireTime.compareTo(targetDateNoMs) >= 0);
+		return prevFireTime;
+	}
+
+	public Date getPrevFireTime(final Date targetDate) {
+		return getTimeBefore(targetDate);
+	}
+
+	private long findMinIncrement() {
+		if (seconds.size() != 1) {
+			return minInSet(seconds) * 1000;
+		}
+		else if (seconds.first() == ALL_SPEC_INT) {
+			return 1000;
+		}
+		if (minutes.size() != 1) {
+			return minInSet(minutes) * 60000;
+		}
+		else if (minutes.first() == ALL_SPEC_INT) {
+			return 60000;
+		}
+		if (hours.size() != 1) {
+			return minInSet(hours) * 3600000;
+		}
+		else if (hours.first() == ALL_SPEC_INT) {
+			return 3600000;
+		}
+		return 86400000;
+	}
+
+	private int minInSet(final TreeSet<Integer> set) {
+		int previous = 0;
+		int min = Integer.MAX_VALUE;
+		boolean first = true;
+		for (final int value : set) {
+			if (first) {
+				previous = value;
+				first = false;
+				continue;
+			}
+			else {
+				final int diff = value - previous;
+				if (diff < min) {
+					min = diff;
+				}
+			}
+		}
+		return min;
+	}
+
+	protected boolean isLeapYear(final int year) {
+		return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+	}
+
+	protected int getLastDayOfMonth(final int monthNum, final int year) {
+		switch (monthNum) {
+			case 1:
+				return 31;
+			case 2:
+				return (isLeapYear(year)) ? 29 : 28;
+			case 3:
+				return 31;
+			case 4:
+				return 30;
+			case 5:
+				return 31;
+			case 6:
+				return 30;
+			case 7:
+				return 31;
+			case 8:
+				return 31;
+			case 9:
+				return 30;
+			case 10:
+				return 31;
+			case 11:
+				return 30;
+			case 12:
+				return 31;
+			default:
+				throw new IllegalArgumentException("Illegal month number: "
+						+ monthNum);
+		}
+	}
+
+	private class ValueSet {
+		public int value;
+		public int pos;
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/shedlock/ShedLockAutoConfigure.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/shedlock/ShedLockAutoConfigure.java
new file mode 100644
index 000000000..eb480469b
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/shedlock/ShedLockAutoConfigure.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.shedlock;
+
+import javax.sql.DataSource;
+
+import com.alibaba.cloud.scheduling.SchedulingConstants;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.init.DataSourceInitializer;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+
+/**
+ * @author yaohui
+ **/
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter(DataSourceAutoConfiguration.class)
+@EnableSchedulerLock(defaultLockAtMostFor = "1m")
+@ConditionalOnBean(DataSource.class)
+@ConditionalOnProperty(name = SchedulingConstants.SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY, havingValue = "shedlock")
+public class ShedLockAutoConfigure {
+
+	private static final String SCHEDULED_LOCK_SCHEMA_PATH = "shedlock/schema/schema-mysql.sql";
+
+	@Bean
+	@ConditionalOnMissingBean
+	public LockProvider lockProvider(DataSource dataSource) {
+		return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder()
+				.withJdbcTemplate(new JdbcTemplate(dataSource))
+				.usingDbTime()
+				.build());
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public DataSourceInitializer shedLockDataSourceInitializer(DataSource dataSource) {
+		ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
+		resourceDatabasePopulator.addScript(new ClassPathResource(SCHEDULED_LOCK_SCHEMA_PATH));
+		DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
+		dataSourceInitializer.setDataSource(dataSource);
+		dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
+		return dataSourceInitializer;
+	}
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..0d03ded27
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.alibaba.cloud.scheduling.schedulerx.SchedulerxAutoConfigure,\
+com.alibaba.cloud.scheduling.shedlock.ShedLockAutoConfigure
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..9d41e71e3
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+com.alibaba.cloud.scheduling.schedulerx.SchedulerxAutoConfigure
+com.alibaba.cloud.scheduling.shedlock.ShedLockAutoConfigure
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/shedlock/schema/schema-mysql.sql b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/shedlock/schema/schema-mysql.sql
new file mode 100644
index 000000000..c369b4c0e
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/shedlock/schema/schema-mysql.sql
@@ -0,0 +1,2 @@
+CREATE TABLE IF NOT EXISTS shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
+    locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/test/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpressionTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/test/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpressionTest.java
new file mode 100644
index 000000000..4e415b290
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/test/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpressionTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.scheduling.schedulerx.util;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author yaohui
+ */
+class CronExpressionTest {
+
+	@Test
+	void isValidExpression() {
+		assertThat(CronExpression.isValidExpression("0 0 0 * * ?")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 */5 * * * ?")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 0 8 1 JAN ?")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THU")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THU-SAT")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#1")).isEqualTo(true);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#5")).isEqualTo(true);
+		// false
+		assertThat(CronExpression.isValidExpression("0 0 8 1 JAW ?")).isEqualTo(false);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THP-SAT")).isEqualTo(false);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#0")).isEqualTo(false);
+		assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#6")).isEqualTo(false);
+	}
+
+	@Test
+	void getTimeAfter() throws ParseException {
+		CronExpression cronExpression = new CronExpression("0 0 8 1 JAN ?");
+		Date nextDate = cronExpression.getTimeAfter(new Date());
+		System.out.println(nextDate);
+		assertThat(nextDate).isNotNull();
+
+		cronExpression = new CronExpression("0 */5 * * * ?");
+		nextDate = cronExpression.getTimeAfter(new Date());
+		System.out.println(nextDate);
+		assertThat(nextDate).isNotNull();
+	}
+
+	@Test
+	void getTimeBefore() throws ParseException {
+		CronExpression cronExpression = new CronExpression("0 0 8 1 JAN ?");
+		Date beforeDate = cronExpression.getTimeBefore(new Date());
+		System.out.println(beforeDate);
+		assertThat(beforeDate).isNotNull();
+
+		cronExpression = new CronExpression("0 */5 * * * ?");
+		beforeDate = cronExpression.getTimeBefore(new Date());
+		System.out.println(beforeDate);
+		assertThat(beforeDate).isNotNull();
+	}
+
+}