On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Why Dart Has Versions
- 2. Reading a Version Number
- 3. Release Channels
- 4. Checking Your Version
- 5. The Language Version
- 6. Seeing It Fail
- 7. Raising the Lower Bound
- 8. Pinning One File with // @dart=
- 9. Experiments
- 10. Milestones
- 11. Keeping Up
- 12. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 128 — Dart Language Evolution & Versioning
| Category | 22 — Modern Dart Features |
| Lecture | 128 of 140 |
| Estimated video length | 8 min |
| Target | Dart 3.12.2 |
| Level | Advanced |
Dart gains new syntax several times a year, yet code written years ago still runs. The language version is how Dart does both: every file says which version of the language it was written for.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every output and error message in this lecture is real. Each one was produced by running the code or command shown on Dart 3.12.
Prerequisites
Students should already understand, from Lectures 001–127:
dart create,dart runanddart analyze(Lectures 002, 113, 114)pubspec.yaml, theenvironment: sdk:line and caret constraints (Lecture 104)- records and patterns (Lectures 076–081), null-aware elements (Lecture 046)
dart:io(Lecture 106)
Connection With Previous Lectures
Lecture 001 promised we would come back to how Dart versions work. Here is why it matters.
Ali copies a working program from Sara into his own project, and it fails to compile — the
same SDK, the same code. The difference is one line in each project's pubspec.yaml. To
understand that line, we need to know how Dart numbers its releases and how it switches new
features on.
Learning Objectives
By the end of this lecture you will be able to:
- Read a Dart version number as major, minor and patch
- Name the release channels and which one to use
- Check your SDK version with
dart --version - Explain how the SDK constraint's lower bound sets the language version
- Fix a "requires a higher language version" error by raising that bound
- Pin a single file with a
// @dart=comment - Recognize what
--enable-experimentis for - Place Dart's major features on a version timeline
1. Why Dart Has Versions
Dart is still evolving. A new stable SDK ships roughly every three months, and many releases
add language features: ?value elements in 3.8, dot shorthands in 3.10. A version number tells
you exactly which features and fixes you have.
Analogy — think of editions of a textbook. A new edition adds chapters, but a class that still uses the old edition keeps working with it. Dart lets every project say which "edition" of the language it was written for.
2. Reading a Version Number
Dart uses semantic versioning: three numbers, major.minor.patch.
| Part | In 3.12.1 | Changes when |
|---|---|---|
| major | 3 | breaking changes: old code may stop working |
| minor | 12 | new features, old code still works |
| patch | 1 | bug fixes only |
This is why the caret from Lecture 104 works: ^3.0.0 accepts every 3.x.y but not 4.0.0.
3. Release Channels
The same SDK is published on separate channels:
| Channel | What it is | Who uses it |
|---|---|---|
| stable | tested releases, about every three months | everyone — this course |
| beta | previews of the next stable release | people trying features early |
| main (formerly dev) | built from the latest source, can break | Dart's own developers |
Stay on stable unless you have a reason not to.
4. Checking Your Version
dart --versionDart SDK version: 3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"The line gives the version, the channel in brackets, the build date and your platform. A
program can read the same text from Platform.version in dart:io:
import 'dart:io';
void main() {
print(Platform.version);
}3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"5. The Language Version
An SDK understands many versions of the language, not just its own. The language
version is only major.minor — patches never change the language — and each package
chooses its own through the lower bound of its SDK constraint:
environment:
sdk: ^3.0.0 # lower bound 3.0.0 → language version 3.0The reasoning: a package that says "I work on Dart 3.0" must not use anything added after 3.0, or users on 3.0 could not compile it. So Dart compiles its files as Dart 3.0, even on a 3.12 SDK.
6. Seeing It Fail
Let's prove it with a scratch project. Its pubspec.yaml:
name: shop
environment:
sdk: ^3.0.0And bin/shop.dart uses dot shorthands, added in Dart 3.10 (.medium instead of
Size.medium; Lecture 129 covers them):
enum Size { small, medium, large }
void main() {
Size size = .medium;
print('Size: ${size.name}');
}dart analyze names the exact fix:
error - bin/shop.dart:4:15 - This requires the 'dot-shorthands' language feature to be enabled. Try updating your pubspec.yaml to set the minimum SDK constraint to 3.10.0 or higher, and running 'pub get'. - experiment_not_enableddart run refuses too, with a less helpful hint:
bin/shop.dart:4:15: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
Try passing the '--enable-experiment=dot-shorthands' command line option.
Size size = .medium;
^The feature is not experimental at all — it is simply newer than language 3.0. Trust the analyzer's suggestion; section 9 explains why the flag would not help.
7. Raising the Lower Bound
Change one line and fetch again:
environment:
sdk: ^3.10.0dart pub get
dart run bin/shop.dartSize: mediumRaising the bound is a promise: from now on, this project needs Dart 3.10 or newer.
8. Pinning One File with // @dart=
A comment at the top of a file, before any code, overrides the package's language version for that file only:
// @dart=3.9
enum Size { small, medium, large }
void main() {
Size size = .medium;
print('Size: ${size.name}');
}Even in a ^3.10.0 project this file is compiled as 3.9, so .medium fails again with the
same error. The override can also go up — // @dart=3.10 lets one file use dot
shorthands in a ^3.0.0 project — but never past what your SDK knows:
bin/high.dart:1:1: Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.
// @dart=3.13
^^^^^^^^^^^^^These comments exist mainly to migrate a big project one file at a time. Day to day, let the pubspec decide.
9. Experiments
Before a feature ships, the Dart team lets people try it behind a flag:
dart run --enable-experiment=<feature-name> bin/main.dartThe flag only unlocks features that are not released yet. Once a feature ships, it is
controlled by the language version instead. That is why
dart run --enable-experiment=dot-shorthands still prints the error from section 6. Never use
experiments in real projects: the feature can change or disappear.
10. Milestones
Each version below comes from the compiler itself: pinning a file one version lower makes the feature fail with a message naming this version.
| Language version | What arrived | Studied in |
|---|---|---|
| 2.12 | sound null safety (int?, !, late) | Lectures 012–016 |
| 3.0 | records, patterns, switch expressions, class modifiers; null safety required | 025, 076–082 |
| 3.3 | extension types | 070 |
| 3.6 | digit separators (1_000_000) | 009 |
| 3.7 | wildcard variables (_ can be repeated) | — |
| 3.8 | null-aware elements (?value) | 046 |
| 3.10 | dot shorthands (.medium) | 129 |
| 3.12 | private named parameters | 131 |
Dart 3 dropped the old pre–null-safety language entirely:
Error: The specified language version 2.11 is too low. The lowest supported language version is 2.12.We will study class modifiers in Lecture 130 and private named parameters in Lecture 131.
11. Keeping Up
- dart.dev — the "Language evolution" page lists which version added each feature.
- The SDK changelog — every release's changes, linked from dart.dev.
dart --version— check what you have before using a new feature.- Read the analyzer's message first: it names the version a feature needs.
12. A Real-World Example
A program that reports which features your SDK supports, combining Platform.version,
records and a version comparison:
import 'dart:io';
(int, int) sdkVersion() {
List<String> parts = Platform.version.split(' ').first.split('.');
return (int.parse(parts[0]), int.parse(parts[1]));
}
bool supports((int, int) have, (int, int) need) {
var (haveMajor, haveMinor) = have;
var (needMajor, needMinor) = need;
if (haveMajor != needMajor) return haveMajor > needMajor;
return haveMinor >= needMinor;
}
void main() {
Map<String, (int, int)> features = {
'records and patterns': (3, 0),
'digit separators': (3, 6),
'dot shorthands': (3, 10),
'a future feature': (3, 14),
};
var (major, minor) = sdkVersion();
print('Your SDK: $major.$minor');
features.forEach((name, need) {
String mark = supports((major, minor), need) ? 'yes' : 'no ';
print(' $mark $name');
});
}Your SDK: 3.12
yes records and patterns
yes digit separators
yes dot shorthands
no a future featureCompare majors first: 4.0 is newer than 3.10 even though 0 < 10.
Code Examples
Example 1 — Printing the SDK Version
import 'dart:io';
void main() {
print(Platform.version);
}3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"Example 2 — Major, Minor and Patch
import 'dart:io';
void main() {
String number = Platform.version.split(' ').first;
List<String> parts = number.split('.');
print('Major: ${parts[0]}');
print('Minor: ${parts[1]}');
print('Patch: ${parts[2]}');
}Major: 3
Minor: 12
Patch: 1Example 3 — Reading the Channel
import 'dart:io';
void main() {
String version = Platform.version;
int start = version.indexOf('(') + 1;
int end = version.indexOf(')');
print('Channel: ${version.substring(start, end)}');
}Channel: stableExample 4 — Comparing Versions
bool isAtLeast((int, int) have, (int, int) need) {
var (haveMajor, haveMinor) = have;
var (needMajor, needMinor) = need;
if (haveMajor != needMajor) return haveMajor > needMajor;
return haveMinor >= needMinor;
}
void main() {
print(isAtLeast((3, 12), (3, 10)));
print(isAtLeast((3, 9), (3, 10)));
print(isAtLeast((4, 0), (3, 10)));
}true
false
trueExample 5 — A Feature Timeline
void main() {
Map<String, String> features = {
'null safety': '2.12',
'records and patterns': '3.0',
'extension types': '3.3',
'digit separators': '3.6',
'wildcard variables': '3.7',
'null-aware elements': '3.8',
'dot shorthands': '3.10',
};
features.forEach((name, version) {
print('${version.padRight(5)} $name'); // pad to 5 characters
});
}2.12 null safety
3.0 records and patterns
3.3 extension types
3.6 digit separators
3.7 wildcard variables
3.8 null-aware elements
3.10 dot shorthandsExample 6 — A File Pinned to 3.6
// @dart=3.6
void main() {
int population = 1_500_000;
print('Population: $population');
}Population: 1500000At // @dart=3.5 the same file fails: This requires the experimental 'digit-separators' language feature to be enabled.
Example 7 — Student Marks (needs 3.0)
// Needs language 3.0 or higher: records and patterns.
(String, int) best(Map<String, int> marks) {
var top = marks.entries.reduce((a, b) => a.value >= b.value ? a : b);
return (top.key, top.value);
}
void main() {
var (name, mark) = best({'Ali': 82, 'Sara': 91, 'Zayan': 77});
print('Top student: $name with $mark');
}Top student: Sara with 91Example 8 — Shopping List (needs 3.8)
// Needs language 3.8 or higher: null-aware elements.
String? extra(bool wanted, String label) => wanted ? label : null;
void main() {
List<String> lines = [
'Notebook',
?extra(false, 'Coupon'),
?extra(true, 'Gift wrap'),
];
print(lines);
}[Notebook, Gift wrap]Example 9 — Bank Deposits (needs 3.7)
// Needs language 3.7 or higher: wildcard variables.
void main() {
List<(String, int)> deposits = [('Mon', 500), ('Tue', 250), ('Wed', 1000)];
int balance = 0;
for (var (_, amount) in deposits) {
balance += amount;
}
print('Balance: $balance');
}Balance: 1750Example 10 — A Version Gate
import 'dart:io';
void main() {
List<String> parts = Platform.version.split(' ').first.split('.');
int major = int.parse(parts[0]);
int minor = int.parse(parts[1]);
if (major < 3 || (major == 3 && minor < 10)) {
print('Please install Dart 3.10 or newer.');
return;
}
double celsius = 25;
double fahrenheit = celsius * 9 / 5 + 32;
print('Running on Dart $major.$minor');
print('$celsius °C = $fahrenheit °F');
}Running on Dart 3.12
25.0 °C = 77.0 °FCommon Beginner Mistakes
1. Following the --enable-experiment hint for a released feature
Try passing the '--enable-experiment=dot-shorthands' command line option.The flag does nothing for a shipped feature. Raise the SDK lower bound instead.
2. Raising the upper bound instead of the lower one
# environment:
# sdk: '>=3.0.0 <4.0.0' → '>=3.0.0 <5.0.0' changes nothingOnly the lower bound sets the language version.
3. Forgetting dart pub get after editing the constraint
The analyzer's message says it: update the pubspec, and run pub get.
4. Placing // @dart= below other code
warning - late.dart:2:4 - The language version override must be specified before any declaration or directive. Try moving the language version override to the top of the file. - invalid_language_version_overrideBelow an import, the comment is ignored. Only other comments may come above it.
5. A version the SDK does not know
Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.Upgrade the SDK first.
6. Comparing versions as text
'3.10'.compareTo('3.9') says 3.10 is smaller, because '1' sorts before '9'. Compare the
numbers, as Example 4 does.
Best Practices
- Use the stable channel and keep the SDK reasonably current.
- Set the SDK lower bound to the oldest version you really support — no lower, no higher.
- Raise the lower bound deliberately, when you want a feature, and run your tests afterwards.
- Use
// @dart=comments only during migrations. - Never ship code that needs
--enable-experiment. - Read the release notes on dart.dev before upgrading.
Mini Challenge
Given the releases 2.12.0, 3.0.0, 3.10.4 and 3.12.1, print each one with the
language version it provides.
Try it yourself before opening the solution.
Show solution
void main() {
List<String> releases = ['2.12.0', '3.0.0', '3.10.4', '3.12.1'];
for (String release in releases) {
List<String> parts = release.split('.');
print('$release -> language ${parts[0]}.${parts[1]}');
}
}2.12.0 -> language 2.12
3.0.0 -> language 3.0
3.10.4 -> language 3.10
3.12.1 -> language 3.12The patch number is dropped: patches never change the language.
Practice Questions
Beginner
- In the version
3.12.1, which number is the major, the minor and the patch? - Which command prints your Dart SDK version and channel?
- A pubspec says
sdk: ^3.6.0. Which language version do its files use?
Application
- Write a program that prints only the patch number of your SDK, as
Patch: …. - A project with
sdk: ^3.7.0uses?valuenull-aware elements. What happens, and how do you fix it? - Inside a
^3.12.0project, pin one file to language 3.0 and print the record('Sara', 91). - Assuming a Dart 3 SDK, print
Dot shorthands: availableorDot shorthands: missing. - What happens when a file starts with
// @dart=3.13on a 3.12 SDK?
Challenge
- Write
parseVersionthat turns'3.10.4'into a record(3, 10, 4), then print its parts and the language version. - A team's pubspec says
^3.0.0, and one file needs dot shorthands. Give two different fixes, and say which affects the whole project.
Practice Answers
Question 1 — Solution
Major 3, minor 12, patch 1.
Question 2 — Solution
dart --version, which prints e.g. Dart SDK version: 3.12.1 (stable) ….
Question 3 — Solution
Language 3.6 — the lower bound's major and minor.
Question 4 — Solution
import 'dart:io';
void main() {
String number = Platform.version.split(' ').first;
print('Patch: ${number.split('.')[2]}');
}Patch: 1Question 5 — Solution
The code is compiled as language 3.7, so it fails with This requires the experimental 'null-aware-elements' language feature to be enabled. Raise the bound to ^3.8.0 and run
dart pub get.
Question 6 — Solution
// @dart=3.0
void main() {
var student = ('Sara', 91);
print(student);
}(Sara, 91)Question 7 — Solution
import 'dart:io';
void main() {
List<String> parts = Platform.version.split(' ').first.split('.');
int minor = int.parse(parts[1]);
print(minor >= 10 ? 'Dot shorthands: available' : 'Dot shorthands: missing');
}Dot shorthands: availableQuestion 8 — Solution
Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.Question 9 — Solution
(int, int, int) parseVersion(String text) {
List<int> parts = text.split('.').map(int.parse).toList();
return (parts[0], parts[1], parts[2]);
}
void main() {
var (major, minor, patch) = parseVersion('3.10.4');
print('Major $major, minor $minor, patch $patch');
print('Language version: $major.$minor');
}Major 3, minor 10, patch 4
Language version: 3.10Question 10 — Solution
- Raise the pubspec to
sdk: ^3.10.0and rundart pub get— this affects every file and requires Dart 3.10 for the whole project. - Put
// @dart=3.10at the top of that one file — only that file changes.
The first is the normal fix; the second suits a gradual migration.
Quick Revision
- What does each part of
major.minor.patchsignal? - Which channel should you normally use?
- Which part of the SDK constraint sets the language version?
- Why does a 3.12 SDK compile a
^3.0.0project as Dart 3.0? - Where must a
// @dart=comment go? - What is
--enable-experimentfor, and when does it not help? - Which version brought null safety? Records and patterns?
Key Takeaways
- Dart versions are
major.minor.patch; only a major release may break old code. - stable is for real work; beta and main are previews.
dart --versionandPlatform.versionshow your SDK version and channel.- The lower bound of
environment: sdk:sets the language version (major.minor). - A newer feature in an older-language project fails;
dart analyzenames the version to raise to. // @dart=x.yat the top of a file overrides the language version for that file.--enable-experimentis only for unreleased features.- Milestones: null safety 2.12, records/patterns 3.0, extension types 3.3, dot shorthands 3.10.
What's Next?
Lecture 129 — Dot Shorthands. We used .medium only to trigger a version error. Next we
will learn the feature properly: where Dart lets you drop the type name, how it decides which
type you mean, and when the full name is still clearer.