Timoral vs Spring Batch: 解决企业Java的静态工作失败问题

2026年8月28日2 次浏览来源:Dev.to阅读原文

正文保留英文原文(机翻易破坏代码与排版),标题/摘要已提供中文

Enterprise Java teams commonly reach a point where scheduled jobs become a liability rather than an asset.

Jobs fail silently — catching exceptions, logging nothing meaningful, and returning success to the scheduler.

Others run simultaneously because the previous execution didn't finish before the next trigger fired and nobody configured .

Data corruption from concurrent runs can take weeks to surface.

None of this shows up in dashboards until a data audit flags inconsistencies in a critical report.

This guide explains why breaks down at scale, how Temporal's durable execution model eliminates these problems, and how to migrate a production Spring Batch pipeline with complete code examples.

What Temporal Actually Is (In One Paragraph) Temporal is a durable execution platform.

You write plain Java code — functions, loops, try/catch — and Temporal makes it fault-tolerant by recording every state transition to an event log.

If the process crashes mid-execution, it replays the log and resumes from exactly where it left off.

There's no external state machine to define, no checkpoint tables to maintain, no retry logic to write.

The code IS the workflow.

The Problem With @Scheduled at Scale works fine for one or two simple jobs.

By the time a team has dozens, accumulated debt becomes significant: No visibility.

Which jobs ran?

Which failed?

How long did they take?

Spring's scheduler offers nothing here by default.

Teams bolt on Actuator, add custom logging, hook up Micrometer.

By the time there's real observability it's a custom framework to maintain.

No fault tolerance.

An exception kills the job instance.

Whether it retries, and how, is the team's problem.

Teams solve this inconsistently — some catch-and-retry inline, some use Spring Retry, some let it silently fail and rely on the next scheduled trigger.

No distributed locking. with ShedLock or Quartz clustering works, but it's an additional library, additional config, and an additional failure mode (what happens when the lock row gets corrupted?).

Testing is painful.

Unit tests can cover job logic, but testing scheduling behavior — does it actually retry? does it respect the lock? — requires either waiting for real time to pass or mocking the scheduler in ways that diverge from production behavior.

Spring Batch adds its own complexity.

Job metadata tables (, , etc.) need to be managed, pruned, and kept consistent.

The restart/skip/retry model is powerful but verbose to configure.

And Spring Batch has no concept of a workflow spanning multiple jobs — that becomes custom orchestration code.

Before: A Typical Spring Batch Job Here's a representative pattern — daily invoice reconciliation, reading from a table, processing, and writing to : This is roughly 150 lines across three files for one job.

It has per-item retry (good), but no workflow-level retry, no timeout enforcement, no alerting on failure, and no way to see execution history without querying the batch tables directly.

After: The Same Job in Temporal The workflow body is 12 lines.

It reads like the business requirement.

No chunk configuration DSL, no step beans, no job parameter boilerplate.

Because the workflow ID includes the date (), attempting to start a second execution for the same day throws — the overlap corruption problem is gone by construction, with no lock to configure or expire.

What You Get for Free Retries with backoff.

Every activity gets the retry policy defined at registration.

Failed activities retry automatically, with exponential backoff, without any code in the activity itself.

Crash recovery.

If the worker process dies mid-workflow, the next worker that picks up the task queue replays the event history and continues from the last completed activity.

No data is lost.

Visibility out of the box.

Temporal's web UI shows every workflow execution: start time, current state, activity history, retry attempts, failures, input and output.

What would take custom Micrometer instrumentation now comes for free.

Workflow ID deduplication.

Using a business-meaningful workflow ID (like ) means the same logical job can never run twice.

This is structurally better than distributed locking.

Testable without real time.

Temporal's lets you test the entire workflow including retries, timeouts, and activity failures — in unit tests, without waiting for real timers.

Common Migration Pitfalls Activity timeouts. must be set conservatively — Temporal cancels and retries the activity if it exceeds the timeout.

Set it to the P99 execution time, not the average.

Activities that occasionally run for 45 minutes on large-backlog days need that reflected in their timeout.

Non-idempotent writers.

Temporal retries activities automatically.

If a writer inserts rows without an upsert, retries produce duplicates.

Every write activity must be idempotent — on the write side before migration.

History size limits.

Temporal's event history has a default limit of 50,000 events.

A workflow processing 500k records in 500-row chunks generates 3,000 activity calls — fine.

Processing row-by-row would hit the limit.

Design activities to operate on chunks, not individual records.

Local dev environment.

Running Temporal locally requires Docker ().

Teams without Docker in their standard dev setup will have friction on day one.

Add it to the team devcontainer or docker-compose before rollout. [!NOTE] Temporal's Java SDK (version 2.x) works with Spring Boot 3.2+ virtual threads automatically.

Pair it with and worker threads scale to thousands of concurrent activity executions on minimal OS threads.

When to Keep Spring Batch Not every job should move to Temporal.

Spring Batch remains the right tool in two scenarios: Regulated data processing with mandatory audit trails.

Spring Batch's job metadata schema (, ) is a ready-made audit log that compliance and operations teams can query directly in the database.

Temporal's event history lives in Temporal's own store — visible in the UI, but not in your RDBMS.

For financial reporting jobs with regulatory requirements, Spring Batch's schema is an asset.

Pure ETL with no orchestration complexity.

A job that reads a file, transforms rows, writes to a table, and exits is exactly what Spring Batch was designed for.

No retry logic, no dependencies between steps, no timeout sensitivity.

Rewriting it in Temporal adds infrastructure without adding value.

Decision rule: if you would describe the job as "a workflow" — with branches, dependencies, or conditions — use Temporal.

If you would describe it as "a batch" — fixed input, transform, fixed output — Spring Batch is the right fit.

Getting Started Temporal's Spring Boot starter auto-wires workers, injects and as beans, and handles graceful shutdown.

Migration Checklist [ ] Inventory all and Spring Batch jobs — map dependencies between them [ ] Classify: "workflow" (Temporal) vs "pure batch" (keep Spring Batch) [ ] Make all activity implementations idempotent (upsert, not insert) [ ] Set at P99 of execution time, not average [ ] Use business-meaningful workflow IDs for deduplication [ ] Add Temporal Docker to dev environment / docker-compose [ ] Run parallel execution for 2 weeks (old scheduler + Temporal) and compare outputs [ ] Cut over the scheduler trigger, remove old beans [ ] Configure Temporal Worker autoscaling (CPU-based HPA works well on Kubernetes) Summary is fine for one or two simple jobs.

Beyond that, teams face silent failures, concurrent execution bugs, and zero visibility — problems that only surface when they've already caused damage.

Temporal solves all three at the infrastructure level: durable execution for crash recovery, workflow ID deduplication for concurrency control, and a built-in UI for visibility.

The workflow code is shorter than Spring Batch equivalent, easier to read, and testable in isolation without mocking time.

For teams with more than 10 scheduled jobs, or any jobs with dependencies and retries, Temporal is worth evaluating before the next production inciden

分享