If you are new to Temporal, one of the most common mistakes is putting too much logic inside an Activity.

It often works at first. Then, retries, failures, and real production behavior expose serious problems.

This post explains what is correct, what is dangerous, and why this pattern matters so much in Temporal.

The Wrong Approach: Doing Work Inside an Activity

@Override
public Result doSomething(Input input) {
    // Lots of logic here
    // Making API calls here
    // BAD
}

At first glance, this looks reasonable. Activities are where work happens, right?

Not exactly.

In Temporal, Activities are retried automatically and may execute multiple times. That changes how you must design them.

When you put complex logic inside an Activity, you introduce several risks.

Duplicate side effects when retries occur
Partial execution during failures
Hard to debug production behavior
Business logic tightly coupled to Temporal

This makes systems fragile and unpredictable.

The Right Approach: Delegate to Existing Code

@Override
public Result doSomething(Input input) {
    return service.doWork(input);
}

Here’s a real example from my AI Fraud Detection repo.

public class FraudDetectionActivityImpl implements FraudDetectionActivity{
    private final FraudDetectionAgent fraudAgent;

    public FraudDetectionActivityImpl(FraudDetectionAgent fraudDetectionAgent) {
        this.fraudAgent = fraudDetectionAgent;
    }

    @Override
    public FraudDetectionResult detectFraud(OrderRequest request) {
        return fraudAgent.analyze(request);
    }
}

This is the correct Temporal pattern.

Here is why it works.

Your business logic lives outside Temporal
Activities become thin and predictable
Domain code is reusable and easy to test
Retries behave safely and consistently

Think of Activities as adapters between your Workflow and the outside world, not the place where thinking happens.

How Temporal Wants Code Structured

Temporal works best when responsibilities are clearly separated.

Workflows

Workflows orchestrate steps and decisions.
They control ordering, retries, time, and state.
They contain deterministic logic only.

Activities

Activities perform a single external side effect.
They call databases, APIs, file systems, or services.
They are safe to retry.

Domain or Service Code

This is where business rules and complexity belong.
It can be tested independently.
It does not depend on Temporal.

This separation is what gives Temporal its reliability and power.

Why This Matters in Production

Because Activities are at least once, you must always ask one question.

What happens if this Activity runs again?

If the answer is any of the following, the Activity is overcomplicating.

It might do something twice
It might partially succeed
I am not sure

Those are warning signs.

A Simple Rule to Remember

If you feel tempted to add branching logic inside an Activity, coordinate multiple steps, or handle partial failures manually, you are probably doing it wrong.

Move that logic into the Workflow for orchestration or into domain services for execution.


Final Takeaway

Temporal Activities should delegate, not think.

The moment an Activity starts thinking, retries become dangerous, and correctness erodes.

Keep Activities thin.
Keep logic reusable.
Let Temporal do what it does best: orchestration.