> For the complete documentation index, see [llms.txt](https://docs.xplatform.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xplatform.com/talon/developing-applications/authoring-user-code/lifecycle/implementing-lifecycle-methods.md).

# Implementing Lifecycle Methods

## Overview

Lifecycle methods allow you to hook into the Talon microservice lifecycle at specific points during startup, operation, and shutdown. The Talon runtime invokes these methods at well-defined stages, enabling you to:

* Receive platform objects via dependency injection
* Provide application objects for annotation scanning
* Initialize resources and state
* React to lifecycle transitions
* Clean up resources during shutdown

Lifecycle methods fall into three categories:

### Injection Methods

The Talon runtime invokes injection methods to provide your application with handles to platform objects. These methods are marked with `@AppInjectionPoint` and allow the runtime to inject objects like the AEP engine, message sender, and configuration descriptors.

### Accessor Methods

The Talon runtime invokes accessor methods to gather application objects containing various annotations. These methods allow you to organize your code across multiple classes while making them discoverable to the Talon runtime.

### Notification Methods

The Talon runtime invokes notification methods to notify your application of lifecycle transitions and operational events. These include initialization, finalization, and lifecycle event handlers.

**See Also**: [Lifecycle](/talon/concepts-and-architecture/microservice-operation/lifecycle.md) - Complete lifecycle flow and timing

***

## Dependency Injection

Use the `@AppInjectionPoint` annotation to receive platform objects from the Talon runtime.

### Injectable Types

The Talon runtime can inject the following types:

| Type                    | Purpose                                                 | Injection Timing              |
| ----------------------- | ------------------------------------------------------- | ----------------------------- |
| **SrvAppLoader**        | Access to application and XVM configuration descriptors | Early in Open phase           |
| **AepEngineDescriptor** | Modify engine configuration before creation             | After HA policy determination |
| **AepMessageSender**    | Send outbound messages                                  | After engine creation         |
| **AepEngine**           | Access to running engine instance                       | After engine creation         |

### Injection on Fields

You can inject directly into fields:

```java
@AppHAPolicy(HAPolicy.EventSourcing)
public class MyApp {
    @AppInjectionPoint
    private AepMessageSender sender;

    @AppInjectionPoint
    private AepEngine engine;

    @EventHandler
    public void onOrder(NewOrderMessage message) {
        // Use sender to send outbound messages
        OrderConfirmation confirmation = OrderConfirmation.create();
        confirmation.setOrderId(message.getOrderId());
        sender.sendMessage("order-confirmations", confirmation);
    }
}
```

### Injection on Methods

Alternatively, inject via setter methods for more control:

```java
@AppHAPolicy(HAPolicy.EventSourcing)
public class MyApp {
    private AepEngine engine;
    private AepMessageSender sender;

    @AppInjectionPoint
    public void setEngine(AepEngine engine) {
        this.engine = engine;
        logger.info("Engine injected: {}", engine.getName());
    }

    @AppInjectionPoint
    public void setSender(AepMessageSender sender) {
        this.sender = sender;
    }
}
```

### Injecting the Application Loader

The application loader provides access to configuration descriptors:

```java
@AppInjectionPoint
public void setLoader(SrvAppLoader loader) {
    // Access XVM configuration
    XRuntime runtime = loader.getXRuntime();

    // Access application descriptor
    SrvAppDescriptor appDescriptor = loader.getAppDescriptor();

    // Access XVM descriptor
    SrvXvmDescriptor xvmDescriptor = loader.getXvmDescriptor();
}
```

### Modifying the Engine Descriptor

Inject the engine descriptor to programmatically configure the engine before it's created:

```java
@AppInjectionPoint
public void setEngineDescriptor(AepEngineDescriptor descriptor) {
    // Configure threading
    descriptor.setInputDisruptorCount(2);

    // Configure adaptive batching
    descriptor.setAdaptiveBatchingEnabled(true);
    descriptor.setAdaptiveBatchingCeiling(100);

    // Configure transaction log
    descriptor.getTransactionLogDescriptor().setEnabled(true);
}
```

{% hint style="info" %}
**Timing**: Engine descriptor injection occurs after the `@AppHAPolicy` is read but before the engine is created. Any HA policy set programmatically in the descriptor will override the annotation value.
{% endhint %}

**See Also**: [AepEngineDescriptor Javadoc](https://build.neeveresearch.com/core/javadoc/LATEST/com/neeve/aep/AepEngineDescriptor.html)

***

## Accessor Methods

Accessor methods allow you to provide objects to the Talon runtime for annotation scanning. This enables you to organize your code across multiple classes while keeping handlers, stats, and configuration discoverable.

### Unified Accessor - @AppIntrospectionPoints

Use `@AppIntrospectionPoints` to provide objects for all types of annotation scanning:

```java
@AppHAPolicy(HAPolicy.EventSourcing)
public class MyApp {
    private OrderHandler orderHandler = new OrderHandler();
    private MetricsCollector metricsCollector = new MetricsCollector();

    @AppIntrospectionPoints
    public void getIntrospectionPoints(Set<Object> objects) {
        objects.add(orderHandler);
        objects.add(metricsCollector);
    }
}

class OrderHandler {
    @AppInjectionPoint
    private AepMessageSender sender;

    @EventHandler
    public void onNewOrder(NewOrderMessage message) {
        // Process order
    }

    @Command(name = "cancelOrder", description = "Cancel an order")
    public String cancelOrder(@Argument(name = "orderId", position = 1) long orderId) {
        // Cancel order
        return "Order " + orderId + " cancelled";
    }
}

class MetricsCollector {
    @AppStat(name = "Orders Processed")
    private volatile long ordersProcessed = 0;

    @Configured(property = "myapp.metricsEnabled", defaultValue = "true")
    private boolean metricsEnabled;
}
```

{% hint style="info" %}
**When to Use**: @AppIntrospectionPoints is called multiple times during the lifecycle (before configuration injection, command handler discovery, event handler discovery, etc.). This allows you to construct objects that depend on earlier lifecycle steps.
{% endhint %}

### Fine-Grained Accessors

For large applications, use fine-grained accessors to reduce the number of objects scanned for each annotation type:

#### @AppConfiguredAccessor

Provide objects containing `@Configured` annotations:

```java
@AppConfiguredAccessor
public void getConfiguredObjects(Set<Object> objects) {
    objects.add(configuredComponent);
}
```

#### @AppEventHandlerContainersAccessor

Provide objects containing `@EventHandler` methods:

```java
@AppEventHandlerContainersAccessor
public void getEventHandlers(Set<Object> containers) {
    containers.add(orderHandler);
    containers.add(paymentHandler);
}
```

#### @AppCommandHandlerContainersAccessor

Provide objects containing `@Command` methods:

```java
@AppCommandHandlerContainersAccessor
public void getCommandHandlers(Set<Object> containers) {
    containers.add(adminCommands);
}
```

#### @AppStatContainersAccessor

Provide objects containing `@AppStat` annotations:

```java
@AppStatContainersAccessor
public void getStatContainers(Set<Object> containers) {
    containers.add(metricsCollector);
}
```

### Programmatic Event Handler

For advanced use cases, provide a programmatic event handler instead of using annotations:

```java
@AppEventHandlerAccessor
public IEventHandler getDefaultEventHandler() {
    return new IEventHandler() {
        @Override
        public void onEvent(IEvent event) {
            // Handle all events programmatically
        }

        @Override
        public void onMessage(MessageView message, MessageView state) {
            // Handle all messages programmatically
        }
    };
}
```

{% hint style="warning" %}
**Note**: The default event handler is only invoked for events/messages not handled by `@EventHandler` annotated methods (unless configured with `DefaultHandlerDispatchPolicy=DispatchAlways`).
{% endhint %}

**See Also**:

* [Annotations Reference](/talon/reference/annotations.md) - Complete annotation details
* [Injecting Configuration](/talon/developing-applications/authoring-user-code/configuration/injecting-configuration.md) - Using @Configured
* [Implementing Command Handlers](/talon/developing-applications/authoring-user-code/command-and-control/implementing-command-handlers.md) - Using @Command
* [Exposing Application Stats](/talon/developing-applications/authoring-user-code/monitoring/exposing-application-statistics.md) - Using @AppStat

***

## Initialization and Finalization

### Application Initialization - @AppInitializer

The `@AppInitializer` method is invoked after the engine is created and injected but before it is started:

```java
@AppInitializer
public void initialize() {
    logger.info("Initializing application");

    // Load reference data
    loadReferenceData();

    // Initialize connection pools
    initializeConnectionPools();

    // Set up internal data structures
    setupDataStructures();
}
```

**What's Available at Initialization**:

* ✅ Injected platform objects (engine, sender, etc.)
* ✅ Configuration values
* ✅ Application state factory has been provided
* ❌ Engine is not yet started
* ❌ Messaging is not yet connected
* ❌ Store is not yet open

**Thread Safety**:

{% hint style="danger" %}
**Do not access or modify microservice state** in the initializer for State Replication applications. The store is not yet open and state objects are not yet available. Use lifecycle events (like `AepEngineActiveEvent`) for state-dependent initialization.
{% endhint %}

### Application Finalization - @AppFinalizer

The `@AppFinalizer` method is invoked during shutdown after the engine is stopped:

```java
@AppFinalizer
public void finalize() {
    logger.info("Finalizing application");

    // Close external connections
    closeConnections();

    // Release resources
    releaseResources();

    // Flush logs or metrics
    flushMetrics();
}
```

**What's Available at Finalization**:

* ✅ Injected platform objects still available
* ❌ Engine is stopped
* ❌ Messaging is disconnected
* ❌ Store is closed

**See Also**: [Lifecycle - Initialize Application](/talon/concepts-and-architecture/microservice-operation/lifecycle.md#initialize-application)

***

## State Factory for State Replication

For State Replication microservices, provide the state factory via `@AppStateFactoryAccessor`:

```java
@AppHAPolicy(HAPolicy.StateReplication)
public class MyApp {
    @AppStateFactoryAccessor
    public IAepApplicationStateFactory getStateFactory() {
        return new IAepApplicationStateFactory() {
            @Override
            public Repository createState(MessageView view) {
                return Repository.create();
            }
        };
    }

    @EventHandler
    public void onCreateCustomer(CreateCustomerMessage message, Repository repository) {
        Customer customer = Customer.create();
        customer.setProfile(message.getCustomerProfile().copy());
        repository.getCustomers().put(message.getCustomerId(), customer);
    }
}
```

**See Also**: [State Replication Template](/talon/developing-applications/microservice-template/state-replication-template.md)

***

## Synchronous Applications

### Main Method - @AppMain

For microservices that are not event-driven (sender-only applications), implement a main method:

```java
@AppHAPolicy(HAPolicy.EventSourcing)
public class SenderApp {
    @AppInjectionPoint
    private AepMessageSender sender;

    @Configured(property = "sender.messageCount", defaultValue = "1000")
    private int messageCount;

    @Configured(property = "sender.ratePerSecond", defaultValue = "100")
    private int ratePerSecond;

    @AppMain
    public void main() {
        logger.info("Starting sender with {} messages at {} msgs/sec",
            messageCount, ratePerSecond);

        RateLimiter rateLimiter = RateLimiter.create(ratePerSecond);

        for (int i = 0; i < messageCount; i++) {
            rateLimiter.acquire();

            TestMessage message = TestMessage.create();
            message.setSequenceNumber(i);
            message.setTimestamp(System.currentTimeMillis());

            sender.sendMessage("test-messages", message);
        }

        logger.info("Sending complete");
    }
}
```

**Execution**:

* Invoked in a separate thread after the engine becomes primary
* Runs asynchronously from message processing
* Suitable for driving application logic without inbound messages

{% hint style="info" %}
**When to Use**: Use `@AppMain` for applications that generate messages or drive their own operation synchronously rather than reacting to inbound messages.
{% endhint %}

**See Also**: [Lifecycle - Synchronous Applications](/talon/concepts-and-architecture/microservice-operation/lifecycle.md#synchronous-applications)

***

## Handling Lifecycle Events

Use `@EventHandler` to react to lifecycle events dispatched by the Talon runtime.

### Engine Lifecycle Events

#### AepEngineCreatedEvent

Dispatched after the engine is created but before it is started:

```java
@EventHandler
public void onEngineCreated(AepEngineCreatedEvent event) {
    logger.info("Engine created: {}", event.getEngine().getName());
    // Engine configuration is finalized
    // Messaging not yet started
}
```

#### AepMessagingPrestartEvent

Dispatched before the engine attempts to connect to messaging:

```java
@EventHandler
public void onMessagingPrestart(AepMessagingPrestartEvent event) {
    logger.info("About to start messaging");
    // Last chance before messaging connections are established
}
```

#### AepEngineStartedEvent

Dispatched after the engine determines its role (primary or backup):

```java
@EventHandler
public void onEngineStarted(AepEngineStartedEvent event) {
    boolean isPrimary = event.getEngine().isPrimary();
    logger.info("Engine started as {}", isPrimary ? "primary" : "backup");
}
```

#### AepEngineActiveEvent

Dispatched when the engine becomes the active primary:

```java
@EventHandler
public void onEngineActive(AepEngineActiveEvent event) {
    logger.info("Engine is now primary and active");

    // Safe to start background processing
    startBackgroundTasks();

    // Safe to schedule periodic tasks
    scheduler.scheduleAtFixedRate(this::generateReports,
        0, 1, TimeUnit.HOURS);
}
```

{% hint style="success" %}
**Best Practice**: Use `AepEngineActiveEvent` to start background threads, scheduled tasks, or any processing that should only run on the primary instance.
{% endhint %}

#### AepMessagingStartedEvent

Dispatched after messaging startup completes:

```java
@EventHandler
public void onMessagingStarted(AepMessagingStartedEvent event) {
    logger.info("Messaging started successfully");
    // All configured bindings have been attempted
}
```

#### AepEngineStoppedEvent

Dispatched when the engine stops:

```java
@EventHandler
public void onEngineStopped(AepEngineStoppedEvent event) {
    logger.info("Engine stopped");
    // Perform any final cleanup
}
```

### Messaging Lifecycle Events

#### AepChannelUpEvent

Dispatched when a channel connection is established:

```java
@EventHandler
public void onChannelUp(AepChannelUpEvent event) {
    logger.info("Channel up: {}", event.getChannel().getName());
    // Channel is ready to send/receive messages
}
```

#### AepChannelDownEvent

Dispatched when a channel connection is lost:

```java
@EventHandler
public void onChannelDown(AepChannelDownEvent event) {
    logger.warn("Channel down: {}", event.getChannel().getName());
    // Channel is no longer available
}
```

#### AepBusBindingUpEvent

Dispatched when a bus binding is fully operational:

```java
@EventHandler
public void onBindingUp(AepBusBindingUpEvent event) {
    logger.info("Binding up: {}", event.getBinding().getName());
    // All channels for this binding are ready
}
```

### Store Lifecycle Events (State Replication)

#### IStoreMemberUpEvent

Dispatched when a new member joins the store cluster:

```java
@EventHandler
public void onStoreMemberUp(IStoreMemberUpEvent event) {
    logger.info("Store member up: {}", event.getMember().getId());
}
```

#### IStoreMemberInitCompleteEvent

Dispatched when store initialization completes:

```java
@EventHandler
public void onStoreMemberInitComplete(IStoreMemberInitCompleteEvent event) {
    logger.info("Store initialization complete");
    // State is now available for access
    // Safe to perform state-dependent initialization
}
```

#### IStoreBindingRoleChangedEvent

Dispatched when the store binding's role changes:

```java
@EventHandler
public void onStoreRoleChanged(IStoreBindingRoleChangedEvent event) {
    logger.info("Store role changing from {} to {}",
        event.getOldRole(), event.getNewRole());
}
```

### Alert Events

Alert events signal exceptional conditions:

#### AepMessagingFailedEvent

```java
@EventHandler
public void onMessagingFailed(AepMessagingFailedEvent event) {
    logger.error("Messaging failed", event.getCause());
    // Messaging has shut down due to failure
    // Consider alerting operations
}
```

#### AepBusBindingDownEvent

```java
@EventHandler
public void onBindingDown(AepBusBindingDownEvent event) {
    logger.error("Binding down: {}", event.getBinding().getName());
    // Binding has failed
    // Messages may be lost until binding recovers
}
```

**See Also**: [Events Reference](/talon/reference/events.md) - Complete event documentation

***

## Complete Example

Here's a complete microservice demonstrating lifecycle method implementation:

```java
package com.example.orderprocessor;

import com.neeve.aep.AepEngine;
import com.neeve.aep.AepEngineDescriptor;
import com.neeve.aep.AepMessageSender;
import com.neeve.aep.IAepApplicationStateFactory;
import com.neeve.aep.annotations.EventHandler;
import com.neeve.aep.event.AepEngineActiveEvent;
import com.neeve.aep.event.AepEngineCreatedEvent;
import com.neeve.cli.annotations.Configured;
import com.neeve.server.app.SrvAppLoader;
import com.neeve.server.app.annotations.*;
import com.neeve.sma.MessageView;

import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@AppHAPolicy(HAPolicy.StateReplication)
public class OrderProcessorApp {

    // Injected platform objects
    private AepEngine engine;
    private AepMessageSender sender;

    // Configuration
    @Configured(property = "orderprocessor.maxOrderSize", defaultValue = "1000000")
    private int maxOrderSize;

    // Statistics
    @AppStat(name = "Orders Processed")
    private volatile long ordersProcessed = 0;

    // Background processing
    private ScheduledExecutorService scheduler;

    // Additional components
    private OrderValidator orderValidator = new OrderValidator();
    private MetricsCollector metricsCollector = new MetricsCollector();

    /**
     * Inject the application loader for access to configuration
     */
    @AppInjectionPoint
    public void setLoader(SrvAppLoader loader) {
        logger.info("Application loader injected");
    }

    /**
     * Inject and configure the engine descriptor before engine creation
     */
    @AppInjectionPoint
    public void setEngineDescriptor(AepEngineDescriptor descriptor) {
        // Enable adaptive batching for better throughput
        descriptor.setAdaptiveBatchingEnabled(true);
        descriptor.setAdaptiveBatchingCeiling(100);

        logger.info("Engine descriptor configured");
    }

    /**
     * Inject the engine after creation
     */
    @AppInjectionPoint
    public void setEngine(AepEngine engine) {
        this.engine = engine;
        logger.info("Engine injected: {}", engine.getName());
    }

    /**
     * Inject the message sender
     */
    @AppInjectionPoint
    public void setSender(AepMessageSender sender) {
        this.sender = sender;
        logger.info("Message sender injected");
    }

    /**
     * Provide additional objects for annotation scanning
     */
    @AppIntrospectionPoints
    public void getIntrospectionPoints(Set<Object> objects) {
        objects.add(orderValidator);
        objects.add(metricsCollector);
    }

    /**
     * Provide the state factory for State Replication
     */
    @AppStateFactoryAccessor
    public IAepApplicationStateFactory getStateFactory() {
        return new IAepApplicationStateFactory() {
            @Override
            public OrderRepository createState(MessageView view) {
                return OrderRepository.create();
            }
        };
    }

    /**
     * Initialize the application after engine creation
     */
    @AppInitializer
    public void initialize() {
        logger.info("Initializing application with maxOrderSize={}", maxOrderSize);

        // Initialize components
        orderValidator.initialize(maxOrderSize);

        // Create scheduler for background tasks (but don't start tasks yet)
        scheduler = Executors.newScheduledThreadPool(1);

        logger.info("Application initialized");
    }

    /**
     * Handle engine creation
     */
    @EventHandler
    public void onEngineCreated(AepEngineCreatedEvent event) {
        logger.info("Engine created, preparing for startup");
    }

    /**
     * Handle engine becoming active - safe to start background processing
     */
    @EventHandler
    public void onEngineActive(AepEngineActiveEvent event) {
        logger.info("Engine is now active as primary");

        // Start background tasks only on the primary
        scheduler.scheduleAtFixedRate(() -> {
            logger.info("Orders processed: {}", ordersProcessed);
            metricsCollector.recordOrderCount(ordersProcessed);
        }, 60, 60, TimeUnit.SECONDS);

        logger.info("Background tasks started");
    }

    /**
     * Handle inbound order messages
     */
    @EventHandler
    public void onNewOrder(NewOrderMessage message, OrderRepository repository) {
        // Validate order
        if (!orderValidator.validate(message)) {
            logger.warn("Invalid order rejected: {}", message.getOrderId());
            sendRejection(message, "Validation failed");
            return;
        }

        // Create order entity
        Order order = Order.create();
        order.setOrderId(message.getOrderId());
        order.setCustomerId(message.getCustomerId());
        order.setAmount(message.getAmount());
        order.setStatus(OrderStatus.PENDING);

        // Store in repository
        repository.getOrders().put(message.getOrderId(), order);

        // Send confirmation
        OrderConfirmation confirmation = OrderConfirmation.create();
        confirmation.setOrderId(message.getOrderId());
        confirmation.setStatus("ACCEPTED");
        sender.sendMessage("order-confirmations", confirmation);

        // Update stats
        ordersProcessed++;
    }

    /**
     * Handle order cancellations
     */
    @EventHandler
    public void onCancelOrder(CancelOrderMessage message, OrderRepository repository) {
        Order order = repository.getOrders().get(message.getOrderId());
        if (order != null) {
            order.setStatus(OrderStatus.CANCELLED);
            logger.info("Order {} cancelled", message.getOrderId());
        }
    }

    /**
     * Send order rejection
     */
    private void sendRejection(NewOrderMessage message, String reason) {
        OrderRejection rejection = OrderRejection.create();
        rejection.setOrderId(message.getOrderId());
        rejection.setReason(reason);
        sender.sendMessage("order-rejections", rejection);
    }

    /**
     * Clean up resources on shutdown
     */
    @AppFinalizer
    public void finalize() {
        logger.info("Finalizing application");

        // Shut down background tasks
        if (scheduler != null) {
            scheduler.shutdown();
            try {
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    scheduler.shutdownNow();
                }
            } catch (InterruptedException e) {
                scheduler.shutdownNow();
            }
        }

        // Final metrics flush
        metricsCollector.flush();

        logger.info("Application finalized, processed {} orders total", ordersProcessed);
    }
}

/**
 * Order validator component with configuration
 */
class OrderValidator {
    private int maxOrderSize;

    public void initialize(int maxOrderSize) {
        this.maxOrderSize = maxOrderSize;
    }

    public boolean validate(NewOrderMessage message) {
        return message.getAmount() > 0 &&
               message.getAmount() <= maxOrderSize &&
               message.getCustomerId() > 0;
    }
}

/**
 * Metrics collector component with stats
 */
class MetricsCollector {
    @AppStat(name = "Peak Orders Per Minute")
    private volatile long peakOrdersPerMinute = 0;

    public void recordOrderCount(long count) {
        // Update peak if needed
        if (count > peakOrdersPerMinute) {
            peakOrdersPerMinute = count;
        }
    }

    public void flush() {
        // Flush any pending metrics
    }
}
```

***

## Lifecycle Execution Order

Lifecycle methods are invoked in the following order during microservice startup:

| Step | Annotation/Method                                  | Purpose                                    |
| ---- | -------------------------------------------------- | ------------------------------------------ |
| 1    | Load main class                                    | XVM loads the microservice                 |
| 2    | `@AppInjectionPoint` (SrvAppLoader)                | Inject application loader                  |
| 3    | `@AppHAPolicy`                                     | Read HA policy from annotation             |
| 4    | `@AppInjectionPoint` (AepEngineDescriptor)         | Inject engine descriptor for configuration |
| 5    | `@AppConfiguredAccessor`                           | Get objects for configuration injection    |
| 6    | `@Configured`                                      | Inject configuration values                |
| 7    | `@AppCommandHandlerContainersAccessor`             | Get command handler containers             |
| 8    | `@AppStatContainersAccessor`                       | Get stat containers                        |
| 9    | `@AppEventHandlerContainersAccessor`               | Get event handler containers               |
| 10   | `@AppStateFactoryAccessor`                         | Get state factory (State Replication)      |
| 11   | Create engine                                      | Engine is instantiated                     |
| 12   | `AepEngineCreatedEvent`                            | Engine created event dispatched            |
| 13   | `@AppInjectionPoint` (AepEngine, AepMessageSender) | Inject engine and sender                   |
| 14   | `@AppInitializer`                                  | Initialize application                     |
| 15   | Start engine                                       | Engine determines role and starts          |
| 16   | `AepEngineStartedEvent`                            | Engine started event dispatched            |
| 17   | `AepEngineActiveEvent`                             | Engine active event (if primary)           |
| 18   | `@AppMain`                                         | Main method invoked (if present)           |

During shutdown:

| Step | Annotation/Method       | Purpose                         |
| ---- | ----------------------- | ------------------------------- |
| 1    | Stop engine             | Engine stops processing         |
| 2    | `AepEngineStoppedEvent` | Engine stopped event dispatched |
| 3    | `@AppFinalizer`         | Finalize application            |

**See Also**: [Lifecycle - Complete Flow](/talon/concepts-and-architecture/microservice-operation/lifecycle.md)

***

## Best Practices

### ✅ Do

1. **Use AepEngineActiveEvent for primary-only initialization**

   ```java
   @EventHandler
   public void onEngineActive(AepEngineActiveEvent event) {
       // Start background tasks only on primary
       startBackgroundProcessing();
   }
   ```
2. **Use @AppIntrospectionPoints for simple applications**
   * Reduces boilerplate for small to medium applications
   * Single method to provide all objects
3. **Use fine-grained accessors for large applications**
   * Improves startup performance by reducing object scanning
   * Better organization of handler containers
4. **Inject AepEngineDescriptor to configure the engine**

   ```java
   @AppInjectionPoint
   public void setEngineDescriptor(AepEngineDescriptor descriptor) {
       descriptor.setAdaptiveBatchingEnabled(true);
   }
   ```
5. **Use field injection for simplicity**
   * Less code
   * Clear and concise
6. **Clean up resources in @AppFinalizer**

   ```java
   @AppFinalizer
   public void finalize() {
       closeConnections();
       releaseResources();
   }
   ```

### ❌ Don't

1. **Don't access microservice state in @AppInitializer (State Replication)**

   ```java
   @AppInitializer
   public void initialize() {
       // ❌ WRONG: Store not yet open
       // repository.getCustomers().put(1L, customer);

       // ✅ CORRECT: Use AepEngineActiveEvent or message handler
   }
   ```
2. **Don't start background tasks in @AppInitializer**

   ```java
   @AppInitializer
   public void initialize() {
       // ❌ WRONG: Will run on both primary and backup
       // scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.MINUTES);

       // ✅ CORRECT: Start in AepEngineActiveEvent (primary only)
   }
   ```
3. **Don't perform long-running operations in lifecycle methods**
   * Keep initialization fast
   * Use background threads for heavy operations
4. **Don't assume messaging is ready in @AppInitializer**

   ```java
   @AppInitializer
   public void initialize() {
       // ❌ WRONG: Messaging not yet started
       // sender.sendMessage("init-complete", message);

       // ✅ CORRECT: Send in AepEngineActiveEvent
   }
   ```
5. **Don't use @AppMain for event-driven applications**
   * @AppMain is for sender-only applications
   * Event-driven apps should use @EventHandler

### Thread Safety

* **Lifecycle methods are single-threaded**: Only one lifecycle method executes at a time
* **Event handlers are single-threaded**: Message processing is single-threaded by design
* **Background threads require synchronization**: If you start background threads, ensure proper synchronization when accessing shared state
* **Volatile fields for stats**: Use volatile for statistics accessed from background threads

```java
// ✅ CORRECT: Volatile for cross-thread access
@AppStat(name = "Orders Processed")
private volatile long ordersProcessed = 0;

// ✅ CORRECT: Synchronized access from background thread
scheduler.scheduleAtFixedRate(() -> {
    long count = ordersProcessed; // Safe: volatile read
    logger.info("Orders: {}", count);
}, 60, 60, TimeUnit.SECONDS);
```

***

## See Also

### Conceptual

* [Lifecycle](/talon/concepts-and-architecture/microservice-operation/lifecycle.md) - Complete lifecycle flow and timing
* [Development Model](/talon/concepts-and-architecture/microservice-architecture/development-model.md) - Lifecycle method categories

### Reference

* [Annotations](/talon/reference/annotations.md) - Complete annotation reference
* [Events](/talon/reference/events.md) - Lifecycle and runtime events

### How-To Guides

* [Injecting Configuration](/talon/developing-applications/authoring-user-code/configuration/injecting-configuration.md) - Using @Configured
* [Implementing Command Handlers](/talon/developing-applications/authoring-user-code/command-and-control/implementing-command-handlers.md) - Using @Command
* [Exposing Application Stats](/talon/developing-applications/authoring-user-code/monitoring/exposing-application-statistics.md) - Using @AppStat
* [Initializing the Microservice](/talon/developing-applications/authoring-user-code/lifecycle/initializing-the-microservice.md) - Initialization patterns

### Templates

* [Event Sourcing Template](/talon/developing-applications/microservice-template/event-sourcing-template.md) - Event Sourcing example
* [State Replication Template](/talon/developing-applications/microservice-template/state-replication-template.md) - State Replication example
