Dart language evolution

This page lists notable changes and additions to the Dart programming language.

To use a language feature introduced after 2.0, set an SDK constraint no lower than the release when Dart first supported that feature.

For example: To use null safety, introduced in 2.12, set 2.12.0 as the lower constraint in the pubspec.yaml file.

  sdk: '>=2.12.0 <3.0.0'

Changes in each release


Dart 3.4


Released 14 May 2024 | Dart 3.4 announcement

Dart 3.4 made several improvements related to type analysis. These include:

  • Improvements to the type analysis of conditional expressions, if-null expressions and assignments, and switch expressions.
  • Aligning the pattern context type schema for cast patterns with the spec.
  • Making the type schema for the null-aware spread operator (...?) nullable for maps and set literals, to match the behavior of list literals.

Dart 3.3


Released 15 February 2024 | Dart 3.3 announcement

Dart 3.3 added some enchancements to the language:

  • Extension types are a new feature in Dart that allow zero-cost wrapping of an existing type. They are similar to wrapper classes and extension methods, but with implementation differences and different tradeoffs.

    extension type Meters(int value) {
      String get label => '${value}m';
      Meters operator +(Meters other) => Meters(value + other.value);
    void main() {
      var m = Meters(42); // Has type `Meters`.
      var m2 = m + m; // OK, type `Meters`.
      // int i = m; // Compile-time error, wrong type.
      // m.isEven; // Compile-time error, no such member.
      assert(identical(m, m.value)); // Succeeds.
  • Abstract getters are now promotable under the rules of private final field promotion, if there are no conflicting declarations.

Dart 3.2


Released 15 November 2023 | Dart 3.2 announcement

Dart 3.2 added enhancements to flow analysis, including:

  • Expanded type promotion to work on private final fields. Previously only available for local variables and parameters, now private final fields can promote to non-nullable types through null checks and is tests. For example, the following code is now sound:

    class Example {
      final int? _privateField;
      void f() {
        if (_privateField != null) {
          // _privateField has now been promoted; you can use it without
          // null checking it.
          int i = _privateField; // OK
    // Private field promotions also work from outside of the class:
    void f(Example x) {
      if (x._privateField != null) {
        int i = x._privateField; // OK

    For more information on when private final fields can and can't promote, check out Fixing type promotion failures.

  • Corrected inconsistencies in type promotion behavior of if-case statements where the value being matched against throws an exception.

Dart 3.1


Released 16 August 2023 | Dart 3.1 announcement

Dart 3.1 added no new features and made no changes to the language.

Dart 3.0


Released 10 May 2023 | Dart 3.0 announcement

Dart 3.0 introduced several new major language features:

  • Patterns, a new category of grammar that lets you match and destructure values.
  • Records, a new type that lets you aggregate multiple values of different types in a single function return.
  • Class modifiers, a new set of keywords that let you control how a class or mixin can be used.
  • Switch expressions, a new form of multi-way branching allowed where expressions are expected.
  • If-case clauses, a new conditional construct that matches a value against a pattern and executes the then or else branch, depending on whether the pattern matches.

Dart 3.0 also introduced a few breaking language changes:

  • Class declarations without the mixin class modifier can no longer be applied as mixins.
  • It is now a compile time error if a colon (:) is used as the separator before the default value of an optional named parameter. Use an equal sign (=) instead.
  • It is now a compile-time error if a continue statement targets a label that is not attached to a loop statement (for, do, and while) or a switch member.

Dart 2.19


Released 25 January 2023

Dart 2.19 introduced some precautions surrounding type inference. These include:

  • More flow analysis flags for unreachable code cases.
  • No longer delegate inaccessible private names to noSuchMethod.
  • Top-level type inference throws on cyclic dependencies.

Dart 2.19 also introduced support for unnamed libraries. Library directives, used for appending library-level doc comments and annotations, can and should now be written without a name:

/// A really great test library.

Dart 2.18


Released 30 August 2022 | Dart 2.18 announcement

Dart 2.18 enhanced type inference. This change allows information flow between arguments in generic function calls. Before 2.18, if you didn't specify an argument's type in some methods, Dart reported errors. These type errors cited potential null occurrences. With 2.18, the compiler infers the argument type from other values in an invocation. You don't need to specify the argument type inline.

Dart 2.18 also discontinued support for mixin classes that don't extend Object.

To learn more about these features, check out:

Dart 2.17


Released 11 May 2022 | Dart 2.17 announcement

Dart 2.17 expanded enum functionality with enhanced enums. Enhanced enums allow enum declarations to define members including fields, constructors, methods, getters, etc.

Dart 2.17 added support for super-initializer parameters in constructors. Super parameters allow you to avoid having to manually pass each parameter into the super invocation of a non-redirecting constructor. You can instead use super parameters to forward parameters to a superclass constructor.

Dart 2.17 removed some restrictions on named arguments. Named arguments can now be freely interleaved with positional arguments. As of Dart 2.17, you can write the following code:

void main() {
  test(skip: true, 'A test description', () {
    // Very long function body here...

To learn more about these features, check out:

Dart 2.16


Released 3 February 2022 | Dart 2.16 announcement

Dart 2.16 added no new features to the Dart language. It did expand the Dart tools.

Dart 2.15


Released 8 December 2021 | Dart 2.15 announcement

Dart 2.15 improved support for function pointers, known as tear-offs. In particular, constructor tear-offs are now supported.

Dart 2.14


Released 8 September 2021 | Dart 2.14 announcement

Dart 2.14 added the unsigned shift (or triple-shift) operator (>>>). This new operator works like >>, except that it always fills the most significant bits with zeros.

To learn more about these operators, check out bitwise and shift operators.

Dart 2.14 removed some restrictions on type arguments. You can pass type arguments to annotations and use a generic function type as a type argument. As of Dart 2.14, you can write the following code:

@TypeHelper<int>(42, "The meaning")
late List<T Function<T>(T)> idFunctions;
var callback = [<T>(T value) => value];
late S Function<S extends T Function<T>(T)>(S) f;

Dart 2.13


Released 19 May 2021 | Dart 2.13 announcement

Dart 2.13 expanded support for type aliases (typedef). Type aliases used to work only for function types but now work for any type. You can use the new name created with a type alias anywhere the original type could be used.

Dart 2.13 improved the struct support in Dart FFI, adding support for inline arrays and packed structs.

Dart 2.12


Released 3 March 2021 | Dart 2.12 announcement

Dart 2.12 added support for sound null safety. When you opt into null safety, types in your code are non-nullable by default, meaning that variables can't contain null unless you say they can. With null safety, your runtime null-dereference errors turn into edit-time analysis errors.

In Dart 2.12, Dart FFI graduated from beta to the stable channel.

Dart 2.10


Released 1 October 2020 | Dart 2.10 announcement

Dart 2.10 added no new features to the Dart language.

Dart 2.9


Released 5 August 2020

Dart 2.9 added no new features to the Dart language.

Dart 2.8


Released 6 May 2020 | Dart 2.8 announcement

Dart 2.8 didn't add any features to the Dart language. It did contain a number of preparatory breaking changes to improve nullability-related usability and performance for null safety.

Dart 2.7


Released 11 December 2019 | Dart 2.7 announcement

Dart 2.7 added support for extension methods, enabling you to add functionality to any type —-even types you don't control—- with the brevity and auto-complete experience of regular method calls.

The following example extends the String class from dart:core with a new parseInt() method:

extension ParseNumbers on String {
  int parseInt() {
    return int.parse(this);

void main() {
  int i = '42'.parseInt();

Dart 2.6


Released 5 November 2019 | Dart 2.6 announcement

Dart 2.6 introduced a breaking change (dart-lang/sdk#37985). Constraints where Null serves as a subtype of FutureOr<T> now yield Null as the solution for T.

For example: The following code now prints Null. Before Dart 2.6, it printed dynamic. The anonymous closure () {} returns the Null type.

import 'dart:async';

void foo<T>(FutureOr<T> Function() f) { print(T); }

main() { foo(() {}); }

Dart 2.5


Released 10 September 2019 | Dart 2.5 announcement

Dart 2.5 didn't add any features to the Dart language, but it did add support for calling native C code from Dart code using a new core library, dart:ffi.

Dart 2.4


Released 27 June 2019

Dart 2.4 introduces a breaking change dart-lang/sdk#35097.

Dart now enforces covariance of type variables used in super-interfaces. For example: Prior to this release Dart accepted, but now rejects, the following code:

class A<X> {};
class B<X> extends A<void Function(X)> {};

You can now use async as an identifier in asynchronous and generator functions.

Dart 2.3


Released 8 May 2019 | Dart 2.3 announcement

Dart 2.3 added three operators designed to improve code that performs list manipulation, such as declarative UI code.

The spread operator enables unpacking the elements from one list into another. In the following example, the list returned by buildMainElements() is unpacked into the list being passed to the children argument:

Widget build(BuildContext context) {
  return Column(children: [

The collection if operator enables adding elements conditionally. The following example adds a FlatButton element unless the app displays the last page:

Widget build(BuildContext context) {
  return Column(children: [
    if (page != pages.last)
      FlatButton(child: Text('Next')),

The collection for operator enables building repeated elements. The following example adds one HeadingAction element for each section in sections:

Widget build(BuildContext context) {
  return Column(children: [
    for (var section in sections)

Dart 2.2


Released 26 February 2019 | Dart 2.2 announcement

Dart 2.2 added support for set literals:

const Set<String> currencies = {'EUR', 'USD', 'JPY'};

Dart 2.1


Released 15 November 2018 | Dart 2.1 announcement

Dart 2.1 added support for int-to-double conversion, allowing developers to set double values using integer literals. This feature removed the annoyance of being forced to use a double literal (for example, 4.0) when the value was an integer in concept.

In the following Flutter code, horizontal and vertical have type double:

padding: const EdgeInsets.symmetric(
  horizontal: 4,
  vertical: 8,

Dart 2.0


Released 22 February 2018 | Dart 2.0 announcement

Dart 2.0 implemented a new sound type system. Before Dart 2.0, types weren't fully sound, and Dart relied heavily on runtime type checking. Dart 1.x code had to be migrated to Dart 2.

Language versioning


A single Dart SDK can simultaneously support multiple versions of the Dart language. The compiler determines what version the code is targeting, and it interprets the code according to that version.

Language versioning becomes important on the rare occasions when Dart introduces an incompatible feature like null safety. When Dart introduces a breaking change, code that did compile might no longer compile. Language versioning allows you to set each library's language version to maintain compatibility.

In the case of null safety, Dart SDKs 2.12 through 2.19 allowed you to choose to update your code to use null safety. Dart uses language versioning to permit non-null-safe code to run alongside null-safe code. This decision enabled migration from non-null-safe to null-safe code. To review an example of how an app or package can migrate to a new language version with an incompatible feature, check out Migrating to null safety.

Each package has a default language version equal to the lower bound of the SDK constraint in the pubspec.yaml file.

For example: The following entry in a pubspec.yaml file indicates that this package defaults to the Dart 2.18 language version.

  sdk: '>=2.18.0 <3.0.0'

Language version numbers


Dart formats its language versions as two numbers separated with a period. It reads as a major version number and a minor version number. Minor version numbers might introduce breaking changes.

Dart releases might append a patch number to a language version. Patches should not change the language except for bug fixes. To illustrate: Dart 2.18.3 serves as the latest release of the Dart 2.18 SDK language version.

Each Dart SDK supports all of the language versions within its major version number. That means that Dart SDK 2.18.3 supports language versions 2.0 through 2.18 inclusive, but not Dart 1.x.

Deriving the language version from the SDK version implies the following:

  • Whenever a minor version of the SDK ships, a new language version appears. In practice, many of these language versions work in a very similar manner to previous versions and have with full compatibility between them. For example: The Dart 2.9 language works much like the Dart 2.8 language.

  • When a patch release of the SDK ships, it cannot introduce new language features. For example: The 2.18.3 release remains language version 2.18. It must remain compatible with 2.18.2, 2.18.1, and 2.18.0.

Per-library language version selection


By default, every Dart file in a package uses the same language version. Dart identifies the default language version as the lower-bound of the SDK constraint specified in the pubspec.yaml file. Sometimes, a Dart file might need to use an older language version. For example, you might not be able to migrate all the files in a package to null safety at the same time.

Dart supports per-library language version selection. To opt to have a different language version from the rest of a package, a Dart library must include a comment in the following format:

// @dart = <major>.<minor>

For example:

// Description of what's in this file.
// @dart = 2.17
import 'dart:math';

The @dart string must be in a // comment (not /// or /*), and it must appear before any Dart code in the file. Whitespace (tabs and spaces) doesn't matter, except within the @dart and version strings. As the previous example shows, other comments can appear before the @dart comment.

To learn how and why the Dart team developed this versioning method, check out the language versioning specification.