Latest in branch 8.1
8.1.0
Released 29 May 2026
(1 day ago)
SoftwareSymfony
Version8.1
Status
Supported
Supported
PHP versions
PHP ≥ 8.4.0
Initial release8.1.0
29 May 2026
(1 day ago)
Latest release8.1.0
29 May 2026
(1 day ago)
End of bug fixesJan 2027
(Ends in 8 months)
End of security fixesJan 2027
(Ends in 8 months)
Release noteshttps://github.com/symfony/symfony/releases/tag/v8.1.0
Source codehttps://github.com/symfony/symfony/tree/v8.1.0
Documentationhttps://symfony.com/doc/current/index.html
Downloadhttps://symfony.com/download
Symfony 8.1 ReleasesView full list

What Is New in Symfony 8.1

Symfony 8.1 is a significant release that pushes the framework well beyond its traditional HTTP roots. The headline story is first-class support for HTTP-less applications, but there is far more to unpack: sweeping Dependency Injection ergonomics improvements, a heavily upgraded Messenger component tailored for high-throughput workers, a new #[Serialize] attribute that eliminates controller boilerplate, and a reworked Console layer that makes CLI development feel as natural as building web endpoints. Below is a structured overview of everything that changed.

Summary

Category Highlights
New Features HTTP-less application kernel (AbstractKernel + KernelTrait); ServicesBundle and ConsoleBundle; #[Serialize] controller attribute; DeepCloner utility; Method-Based Commands; Console Argument Resolvers; #[AsTagDecorator] for tagged service decoration; AmqpPriorityStamp; Batch fetching for Messenger workers; Improved JSON streaming and JsonPath custom functions
Improvements Env vars autowirable as Closure/Stringable; Stack-based service decorators with decorates_tag; #[Target] + #[AsAlias(target:)] for explicit disambiguation; Configurable service reset interval (--no-reset=N); Idle timeout for batch handlers; Decode failures routed through failure pipeline; Redis ListableReceiverInterface; Redis Cluster via single DSN; Dynamic controller attributes; Improved request payload mapping (file DTOs, variadic args, dynamic validation groups); Translation XLIFF improvements; Improved Cache attribute with closures and conditionals; Validator Clock support and reentrant validators; Console image pasting and interactive choice questions; JsonPath value objects and date handling
Deprecations Implicit parameter-name-based autowiring alias matching (use #[Target] explicitly); getDefaultName() / getDefaultPriority() static methods on tagged services (use #[AsTaggedItem]); HttpKernel\Bundle\BundleInterface, HttpKernel\DependencyInjection\MergeExtensionConfigurationPass, HttpKernel\Config\FileLocator (moved to DependencyInjection component)

Can You Build a Symfony Application Without HttpKernel in 8.1?

Yes -- Symfony 8.1 makes HTTP-less applications a first-class citizen. Until this release, every Symfony application that needed the container lifecycle (bundle loading, configuration, service compilation) was forced to depend on HttpKernel even if it never handled a single HTTP request. Console workers, message consumers, and data pipelines all carried that unnecessary dependency.

The fix is architectural: the kernel and bundle infrastructure have been extracted into the DependencyInjection component. A new Symfony\Component\DependencyInjection\Kernel namespace ships AbstractKernel, KernelTrait, and a standalone KernelInterface that expose only container-related API -- no Request, no Response. Creating a headless kernel is now minimal:

// src/Kernel.php
namespace App;

use Symfony\Component\DependencyInjection\Kernel\AbstractKernel;
use Symfony\Component\DependencyInjection\Kernel\KernelTrait;

class Kernel extends AbstractKernel
{
    use KernelTrait;
}

The same configuration conventions apply: config/bundles.php, config/packages/, and config/services.yaml. HttpKernel\Kernel now extends AbstractKernel internally, so existing HTTP applications require zero changes.

Two new standalone bundles complete the picture:

  • ServicesBundle -- foundational DI services: event dispatcher, filesystem, clock, env var processors.
  • ConsoleBundle -- command registration, argument resolver, error listener. Automatically pulls in ServicesBundle as a dependency.

A new #[RequiredBundle] attribute lets your own bundles declare dependencies on other bundles, resolved recursively in the right boot order. The optional ignoreOnInvalid: true flag silently skips dependencies that are not installed.

Watch out for the three deprecated class aliases: HttpKernel\Bundle\BundleInterface, HttpKernel\DependencyInjection\MergeExtensionConfigurationPass, and HttpKernel\Config\FileLocator have all moved to the DependencyInjection component. The old class names still work as backward-compatible aliases but will be removed in Symfony 9.0.

What Are the Most Important Dependency Injection Changes in Symfony 8.1?

Symfony 8.1 delivers the most comprehensive DI overhaul in several release cycles. The changes range from long-requested ergonomic improvements to important deprecations that affect daily autowiring patterns.

Env vars as Closure or Stringable. In long-running workers (Messenger, FrankenPHP, RoadRunner), injecting an env var as a plain string bakes the value at compile time and never refreshes it. Symfony 8.1 lets you autowire env vars as \Closure or \Stringable so each call returns the current value after a Container::resetEnvCache():

use Symfony\Component\DependencyInjection\Attribute\Autowire;

class Worker
{
    public function __construct(
        #[Autowire(env: 'DB_URL')]
        private \Closure $dbUrl,

        #[Autowire('redis://%env(HOST)%:%env(PORT)%')]
        private \Stringable $redisDsn,
    ) {}
}

A new YAML tag !env_closure exposes the same behaviour for teams that prefer configuration over attributes. An optional second element in the array provides a default value when the variable is missing.

Decorating tagged services declaratively. Wrapping every service carrying a given tag previously required a custom compiler pass. The new decorates_tag option (available both in YAML and via the #[AsTagDecorator] PHP attribute) makes this declarative. The framework clones the decorator once per tagged service and wires the inner reference automatically. This is immediately useful for adding cross-cutting concerns such as logging or tracing to an entire family of services.

Stack-based decorators now support decorates and decorates_tag. Service stacks previously could not decorate an existing service; 8.1 closes that gap. The innermost service in the stack becomes the decorator of the target.

Explicit #[Target] is now mandatory. The legacy behaviour where the parameter name alone could resolve a named autowiring alias is deprecated. It was fragile (renaming a parameter silently broke injection) and invisible from the constructor signature. Going forward, use #[Target('image')] explicitly and pair it with the new target argument on #[AsAlias] on the implementation side. Implicit matching still works but emits a deprecation notice and will be removed in Symfony 9.0.

Additional quality-of-life improvements:

  • Dots are now allowed in env var names (e.g., DATABASE.PRIMARY.URL), enabling consumption of variables generated by hierarchical config tooling.
  • setFactory() and setConfigurator() now accept a Definition instance directly, wrapping it as [$definition, '__invoke'].
  • ContainerConfigurator::import() gains an exclude argument to skip specific files or directories within a glob import pattern.
  • #[AsTaggedItem] can now be applied to security voters to set priority without duplicating the security.voter tag.
  • getDefaultName() and getDefaultPriority() static methods on tagged services are deprecated in favour of #[AsTaggedItem].

How Does Symfony 8.1 Improve the Messenger Component for High-Throughput Workers?

Symfony 8.1 treats Messenger as a first-class production subsystem and adds features that matter most at scale: fewer round-trips, smarter failure handling, and better transport compatibility.

Batch fetching. Workers previously fetched one message per round-trip. A new --fetch-size option tells the transport to retrieve multiple messages in a single network call. SQS supports up to 10 messages per ReceiveMessage call; Redis uses XREADGROUP COUNT; Doctrine uses LIMIT; AMQP repeats basic_get. For high-throughput queues, this alone can significantly reduce transport overhead:

php bin/console messenger:consume async --fetch-size=8

Configurable service reset interval. The --no-reset flag now accepts an integer. Passing --no-reset=100 resets services every 100 messages instead of after every single one, striking a balance between state isolation and performance that was previously impossible without patching the worker itself.

Decode failures enter the retry pipeline. Previously, a message that could not be decoded (for example because its class was deleted after a refactor) was silently discarded. In Symfony 8.1, decode failures are wrapped in a MessageDecodingFailedException envelope and routed through the standard retry and failure transports. A new DecodeFailedMessageMiddleware retries decoding on every attempt, so restoring a missing class or fixing a serializer can recover messages without manual intervention.

AMQP per-message priority. A new AmqpPriorityStamp lets you set the RabbitMQ message priority on dispatch, mirroring the existing BeanstalkdPriorityStamp. Priority is a native RabbitMQ feature; for transports that do not support it (Redis, Doctrine, SQS), the recommended pattern remains consuming multiple named queues in priority order.

Additional Messenger highlights:

  • #[AsMessage(serializedTypeName: 'crawler.event')] replaces the FQCN in the type transport header, making cross-language or cross-namespace interoperability straightforward.
  • BatchHandlerTrait gains an optional getIdleTimeout() method that flushes a partial batch after a configurable period of inactivity -- critical for low-throughput queues where batches rarely fill.
  • PostgreSQL LISTEN/NOTIFY blocking is moved to a dedicated idle-event subscriber so multi-queue priority consumption no longer blocks on the first queue.
  • The Redis transport now implements ListableReceiverInterface, enabling monitoring bundles to inspect pending messages via XRANGE without consuming them.
  • A single-endpoint Redis Cluster DSN (redis://redis-cluster:6379?redis_cluster=true) replaces the fragile approach of enumerating every node.
  • Quorum queue delay strategies are rewritten to use one queue per day, fixing the long-standing expiration issue with delayed messages.
  • Deduplication locks are released immediately when a message is definitively moved to the failure transport, preventing blocked dispatches until TTL expiry.

How Does the New #[Serialize] Attribute Simplify Symfony API Controllers?

The new #[Serialize] controller attribute eliminates the recurring boilerplate of injecting SerializerInterface, calling serialize(), and manually building a JsonResponse. Apply the attribute to any controller method and return a plain PHP object; Symfony handles serialization, content negotiation, and response construction automatically.

use Symfony\Component\HttpKernel\Attribute\Serialize;

final readonly class CreateProductController
{
    #[Serialize(
        code: 201,
        headers: ['X-Custom-Header' => 'abc'],
        context: [DateTimeNormalizer::FORMAT_KEY => 'd.m.Y H:i:s'],
    )]
    public function __invoke(): ProductCreated
    {
        return new ProductCreated(101);
    }
}

The format is derived from the current request, so a route configured as /products/{id}.{_format} returns JSON for /products/42.json and XML for /products/42.xml with the same controller. If the client requests an unsupported format, Symfony automatically responds with 415 Unsupported Media Type.

In practice, this pairs naturally with #[MapRequestPayload] (improved in 8.1 to support uploaded files inside DTOs, variadic arguments, and dynamic validation groups) and the dynamic controller attributes feature that makes PHP attributes on controller methods readable from event listeners at runtime. Together, these three changes push Symfony controllers closer to a pure input/output contract and away from infrastructure wiring.

What Console and Developer-Experience Improvements Does Symfony 8.1 Include?

Symfony 8.1 reworks the Console component significantly, with two headline features that change how teams structure CLI applications.

Method-Based Commands. Multiple related commands can now live in a single class, sharing injected dependencies and reducing the number of service definitions. Each method maps to a separate CLI command, eliminating the previous pattern of one class per command when commands are logically grouped:

use Symfony\Component\Console\Attribute\AsCommand;

class ProductCommands
{
    #[AsCommand('product:import')]
    public function import(): int { /* ... */ return 0; }

    #[AsCommand('product:export')]
    public function export(): int { /* ... */ return 0; }
}

Console Argument Resolvers. CLI arguments and options can now be automatically converted into typed objects using the same resolver infrastructure as HTTP controllers. This brings feature parity with HTTP argument resolvers and removes manual casting/validation boilerplate from command execute() methods.

Improved Console Input. Symfony 8.1 adds image pasting support (useful for terminals like iTerm2 that support inline images), interactive choice questions with real-time filtering, answer validation for interactive prompts, and raw input forwarding for commands that need to pass through stdin unchanged.

DeepCloner. A new DeepCloner utility provides fast, memory-efficient deep cloning of complex PHP object graphs. This matters for any application that needs to snapshot domain objects or clone entity graphs without triggering Doctrine lazy loading or other side effects.

Other DX improvements worth noting: the Cache attribute in 8.1 supports closure-based lastModified and maxAge values, conditional application via expressions, and new expression variables. The Validator component adds Clock support for time-sensitive constraints and introduces reentrant validators to handle circular references. Translation improvements include broader XLIFF support and more flexible locale configuration. JSON streaming gains value object support, better date handling, default options, and custom JsonPath functions.

Frequently Asked Questions about Symfony 8.1

Does upgrading to Symfony 8.1 require changes to existing HTTP applications?
No. HttpKernel\Kernel now extends the new AbstractKernel internally, so existing HTTP applications continue to work without any code changes. The only action required before Symfony 9.0 is to address deprecations: replace implicit parameter-name autowiring with explicit #[Target] usage and swap getDefaultName() / getDefaultPriority() static methods with #[AsTaggedItem] on tagged services.

How do I use the new #[Serialize] attribute and what does it require?
Install and configure the Serializer component, then annotate a controller method with #[Serialize] and return any object or array instead of a Response. Symfony automatically serializes the return value using the request format, sets the Content-Type header, and wraps it in a Response. You can customise the HTTP status code, response headers, and serialization context directly on the attribute, for example #[Serialize(code: 201, context: [DateTimeNormalizer::FORMAT_KEY => 'd.m.Y'])].

What is the migration effort for teams heavily using implicit autowiring aliases?
Moderate, but straightforward. Symfony 8.1 emits a deprecation notice each time an injection is resolved by parameter name alone rather than by an explicit #[Target] attribute. A codebase search for constructor parameters that match named autowiring aliases, followed by adding #[Target('aliasName')] to each injection point and #[AsAlias(Interface::class, target: 'aliasName')] to each implementation, is sufficient. The deprecated behaviour still works and will not break until Symfony 9.0.

How does batch fetching in Messenger work and which transports support it?
Pass --fetch-size=N to messenger:consume and Symfony hints the transport to retrieve up to N messages per round-trip. SQS supports up to 10 messages per ReceiveMessage call, Redis uses XREADGROUP COUNT, Doctrine uses a LIMIT clause, and AMQP repeats basic_get. Transports that do not support bulk fetching silently fall back to single-message retrieval.

Can I build a Symfony console application without FrameworkBundle in 8.1?
Yes. Register only ConsoleBundle in config/bundles.php and extend AbstractKernel with KernelTrait in your kernel class. ConsoleBundle automatically pulls in ServicesBundle as a dependency via the new #[RequiredBundle] mechanism, giving you the event dispatcher, filesystem, clock, command registration, and argument resolver without the full HTTP stack that FrameworkBundle carries.

What happens to messages that fail to decode in Symfony 8.1 Messenger?
They are no longer silently discarded. Symfony 8.1 routes decode failures through the standard failure-handling pipeline: the message remains acknowledgeable, gets wrapped in a MessageDecodingFailedException envelope, and passes through the configured retry and failure transports just like any other failed message. A new DecodeFailedMessageMiddleware retries decoding on every retry attempt, so deploying a fix (restoring a missing class, updating a serializer) can recover the message automatically without manual intervention.

Releases In Branch 8.1

VersionRelease date
8.1.029 May 2026
(1 day ago)
8.1.0-RC127 May 2026
(3 days ago)
8.1.0-BETA320 May 2026
(10 days ago)
8.1.0-BETA213 May 2026
(17 days ago)
8.1.0-BETA106 May 2026
(24 days ago)