5 min read

Symfony 8.1: Build HTTP-Less Applications with the Full DI Container

Symfony 8.1 ships HTTP-less app support, a standalone ConsoleBundle, method-based commands, and the new #[RateLimit] attribute. Here's what changes.

Featured image for "Symfony 8.1: Build HTTP-Less Applications with the Full DI Container"

Symfony 8.1 is landing at the end of May 2026, and it brings one of the more architecturally significant additions in recent memory: the ability to build Symfony applications that have no HTTP layer whatsoever. If you write console tools, message consumers, background workers, or any kind of CLI-first application using Symfony components, this release is squarely aimed at you.

Here is a tour of the most developer-relevant changes in 8.1.

HTTP-Less Applications

The headline feature, contributed by Nicolas Grekas, extracts the kernel and bundle infrastructure out of HttpKernel and into the DependencyInjection component, where it arguably always belonged.

Before 8.1, if you built a console application on Symfony, you still had to pull in HttpKernel even though you never handled a request. That dependency dragged in HttpFoundation as well, which felt like unnecessary weight for a queue worker or a scheduled command runner.

Symfony 8.1 introduces a new Symfony\Component\DependencyInjection\Kernel namespace. To build a headless application, you extend AbstractKernel and include KernelTrait:

// src/Kernel.php
namespace App;

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

class Kernel extends AbstractKernel
{
    use KernelTrait;
}

KernelTrait provides the same container lifecycle you are used to from MicroKernelTrait: it builds, compiles, and caches the service container. It reads config/bundles.php for bundle registration, config/packages/ for configuration, and config/services.yaml for service definitions. None of that changes. What is gone is everything HTTP-related.

The component also introduces a new KernelInterface in the DependencyInjection component. Previously, any class that needed to inspect bundles, check the environment, or reference the cache directory had to type-hint against HttpKernel\KernelInterface, which extended HttpKernelInterface and therefore dragged in the entire HTTP stack. Now you can write:

use Symfony\Component\DependencyInjection\Kernel\KernelInterface;

class BundleInspector
{
    public function __construct(private KernelInterface $kernel)
    {
    }
}

The change is backward compatible. HttpKernel\Kernel now extends the new AbstractKernel, so existing web applications require no changes.

ConsoleBundle and ServicesBundle

Alongside the kernel infrastructure change, the core of FrameworkBundle has been split into two lightweight standalone bundles.

Symfony\Component\DependencyInjection\Kernel\ServicesBundle handles foundational DI services: the event dispatcher, filesystem, clock, and environment variable processors.

Symfony\Component\Console\ConsoleBundle handles everything console-related: command registration, the argument resolver, and the error listener.

A minimal CLI application now only needs this in config/bundles.php:

return [
    Symfony\Component\Console\ConsoleBundle::class => ['all' => true],
];

ServicesBundle loads automatically as a dependency, which brings us to the other new primitive in this release.

#[RequiredBundle]

The #[RequiredBundle] attribute lets you declare bundle-to-bundle dependencies directly in PHP:

use Symfony\Component\DependencyInjection\Kernel\RequiredBundle;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

#[RequiredBundle(AcmeCoreBundle::class)]
#[RequiredBundle(AcmeUtilBundle::class, ignoreOnInvalid: true)]
class AcmeBlogBundle extends AbstractBundle
{
}

The attribute is repeatable and resolves recursively. If bundle A requires B and B requires C, all three load in the correct order. The ignoreOnInvalid: true flag lets you declare soft dependencies on bundles that may or may not be installed without throwing an exception.

This is a small but genuinely useful addition for anyone who ships Symfony bundles, since it makes optional feature bundles much easier to wire up correctly.

Method-Based Commands

Console commands in Symfony 8.1 get significantly less boilerplate. You can now define a command using PHP attributes on any method of a class, rather than extending AbstractCommand and wiring everything manually:

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;

class GenerateReportCommand
{
    #[AsCommand(name: 'app:generate-report', description: 'Generate a sales report')]
    public function __invoke(
        #[Argument] string $format = 'csv',
        #[Option] bool $sendEmail = false,
    ): int {
        // $format and $sendEmail are resolved automatically
        return Command::SUCCESS;
    }
}

Arguments and options are injected directly as typed parameters. No $input->getArgument() calls, no $input->getOption() calls. The method signature is the contract.

This pairs well with the existing #[MapInput] support and validation constraints, which 8.1 also extends. You can now attach Symfony validator constraints directly to console arguments:

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Validator\Constraints as Assert;

#[AsCommand(name: 'app:send')]
public function __invoke(
    #[Argument, Assert\Email] string $recipient,
): int {
    // ...
}

If validation fails, Symfony outputs the constraint message and exits cleanly before your command body runs.

#[RateLimit] for Controllers

Symfony 8.1 adds a #[RateLimit] attribute that lets you declare rate limits directly on controller methods:

use Symfony\Component\RateLimiter\Attribute\RateLimit;

class ApiController extends AbstractController
{
    #[Route('/api/send', methods: ['POST'])]
    #[RateLimit(policy: 'api_send')]
    public function send(Request $request): JsonResponse
    {
        // handled automatically; 429 returned if limit exceeded
    }
}

Rate limit policies are still defined in your framework configuration, but the attribute wires them to specific actions without requiring you to inject the RateLimiterFactory service and check manually. For APIs with per-endpoint limits this is a meaningful reduction in boilerplate.

Additionally, 8.1 adds a calendar-aligned mode to FixedWindowLimiter, which is useful for limits that should reset on the hour, at midnight, or on the first of the month rather than counting from the first request.

Other Noteworthy Changes

A few additional things worth knowing about 8.1:

The GuzzleHttpHandler is new in HttpClient, allowing you to use Symfony’s HTTP client as a drop-in Guzzle handler. If you depend on Guzzle for SDK compatibility but want Symfony’s retry logic and tracing, this bridges the gap cleanly.

CachingHttpClient now defaults its $maxTtl to 86400 seconds instead of no limit, which prevents accidental eternal cache entries in long-running workers.

The VarDumper component adds CSP nonce support to HtmlDumper, and the Dump class-strings now render as class stubs with source location and static properties visible in the profiler. Debugging complex object graphs becomes considerably easier.

Three HttpKernel classes have been moved to DependencyInjection with backward-compatible aliases that are now deprecated. If your code type-hints HttpKernel\Bundle\BundleInterface, HttpKernel\DependencyInjection\MergeExtensionConfigurationPass, or HttpKernel\Config\FileLocator directly, update those imports before the aliases are removed in a future major version.

Upgrading

Symfony 8.1 requires PHP 8.4. If you are already on Symfony 8.0, the upgrade path should be smooth: most of the changes in 8.1 are additive, and the deprecations are gentle. Run composer require symfony/symfony:8.1.* in a branch, address any deprecation notices in the profiler, and test your console commands especially carefully since that is where the most surface area changed.

The official upgrade guide is available at symfony.com/doc/current/setup/upgrade_major.html and SymfonyInsight can generate an automated upgrade report that flags the specific code paths in your project that need attention.

Sources