
Nayden GochevDart for Java Developers A deep, practical mapping from Java to Dart: syntax, type system,...
A deep, practical mapping from Java to Dart: syntax, type system, concurrency, tooling, Flutter, and backend (Shelf, Relic, Serverpod, and the broader pub.dev ecosystem). This guide is written for engineers who already know Java and want fast mental-model transfer plus honest gaps (what one language has that the other does not).
Note on sources: This guide draws on Learn Dart in Y Minutes (community tutorial; the bundled
learndart.dartmixes teaching with legacy phrasing—prefer this guide + dart.dev for sound null safety facts) and Google’s archived Flutter codelab Intro to Dart for Java Developers (Bicycle → Rectangle → factories → functional iteration).
Future, Stream, async/await, isolatespart, export, packagesdart:* libraries — Java mental anchorsswitch expressionstypedef and type aliasesintl)typed_data vs NIOfor-in vs Javadart:ffi vs JNI (sketch)| Idea | Java | Dart |
|---|---|---|
| Primary runtimes | JVM (bytecode), Graal native | VM (JIT dev), AOT (release native), also JS & Wasm targets for web |
| Unit of compilation / reuse |
.java → packages + modules (JPMS optional) |
Every .dart file is a library; packages on pub.dev |
| Null | Nullable reference types (Java 8+ annotations; JEP 804 modern nullable types in recent Java) |
Sound null safety (non-null by default); T? for nullable |
| Concurrency | Threads, ExecutorService, virtual threads (Project Loom, Java 21+) |
Single-threaded event loop in isolate + explicit isolates (no shared mutable heap) |
| OOP style | Classes + interfaces | Classes + implicit interfaces + mixins |
| Polymorphism tricks | Abstract class, default interface methods | Extension methods, mixins, factory constructors |
| Async |
CompletableFuture, reactive stacks |
Future/Stream, async/await first-class
|
| Builds | Maven / Gradle |
dart pub + SDK tools; Flutter adds its build |
| “Enterprise” servers | Huge ecosystem | Growing: Serverpod, Relic, Shelf, etc. |
Bottom line: Dart optimizes for fast iteration, UI (Flutter), and multi-target compilation. Java optimizes for massive enterprise libraries, JVM depth, and decades of JVM tooling.
if/for/while/switch).var / final vs Java’s var since Java 10).free.| Topic | Java intuition | Dart reality |
|---|---|---|
new |
new Foo() everywhere |
new optional / uncommon in modern style: Foo()
|
| One class per file | Convention |
Library = file; part / part of split implementation |
| Overloading | Common | No method overloads — use named args, different method names |
static utils |
Collections, StringUtils, … |
Less culture of static bags — top-level functions, extensions |
| Null |
@Nullable, Optional, NPE |
Types express null (String vs String?) — sound |
| Threads | Use freely (with care) | Isolate model; no shared memory between isolates |
| Checked exceptions | throws |
No checked exceptions — errors are unchecked style |
| Wildcard generics | List<? extends Number> |
Dart generics different (see generics section) |
External primers worth bookmarking:
learndart.dart tour: comments, var/final/const, nested functions, Iterable/List/Map shapes ((), [], {}), loops, ~/, generators, mixins, cascades, null-aware operators. Caveat: some prose predates sound null safety; treat runtime/typing claims in that file with skepticism and follow Dart 3 rules here.Bicycle → Dart: main at top level, new optional, this.field constructor shorthand, privacy via _, getters, no constructor overloading replaced by named parameters / defaults, factory constructors, implements without interface keyword, map/forEach functional style.| Topic | Dart (what to remember) |
|---|---|
Module = .dart file |
A library is the file; top-level functions, variables, const, and classes coexist (like Kotlin files). |
| Library privacy | Names starting with _ are private to the library (the .dart file / merged part), not “private to the class” like Java private. |
| Everything is an object |
int, double, bool, Null — unified model; no Java-style primitive-only stack semantics. |
| Null safety | Append ? for nullable types (String?); flow analysis narrows types — Kotlin-like, not optional typing. |
| Generics & reification | Type arguments to generic classes are reified (List<int>.runtimeType knows int). Java erases type parameters at runtime (with erasure quirks + reflection metadata). Footnote: generic function instantiations still have limits—don’t treat runtime reflection as “full templates.” |
| Inference |
var x = 1; — var is like Java 10+ var (inferred fixed type, not dynamic reassignment to another type). |
final vs const |
final = single assignment (runtime). const = compile-time constant object graph (must be constructible const). |
| Imports |
dart: SDK (import 'dart:convert';), relative (import 'src/foo.dart';), package: (import 'package:test/test.dart';). Use as, show, hide, deferred as to tame namespaces. |
| Async |
async/await, Future<T>, Stream<T> on the current isolate; heavy CPU → isolates (dart:isolate). |
| Generators |
sync* / async* + yield / yield* for lazy iterables and async streams. |
| Callable classes | Implement call so myObj(x) works like a function. |
typedef |
Type aliases + function types: typedef Pred = bool Function(Event e);
|
| Control flow |
for (e in xs), while, do-while; switch in Dart 3 supports patterns / exhaustiveness (unlike Java’s old switch only story). |
| Extensions |
extension X on String { ... } — static dispatch on static type of receiver (like Kotlin). |
| Immutability codegen | See freezed on pub.dev for union types + copyWith (think “data classes + sealed” ergonomics). |
Dates are approximate release periods; exact patch versions vary. For authoritative per-version changes, see Dart language evolution.
dynamic escape hatches broadly).| Era | Features (examples) |
|---|---|
| Dart 2.17 | Enhanced enums (fields, methods, constructors) |
| Dart 2.19 / 3.0 prep |
Records, patterns, class modifiers (sealed, final, interface, mixin class) |
| Dart 3.0 |
Records (x, y), pattern matching in switch, sealed types, destructuring |
| Dart 3.x | Incremental improvements (macro experiments, documentation updates, analyzer) |
| Recent SDK lines | Productivity: dot shorthands and tooling (see current SDK changelog for your version) |
Java parallels for the same decades: lambdas (Java 8), modules JPMS (9), var (10), records (16), pattern matching for switch (17–21), virtual threads (21), etc. Dart moved quickly on UI-driven language features (e.g. records/patterns) because Flutter benefits directly.
Java
public class Main {
public static void main(String[] args) {
System.out.println("Hello");
}
}
Dart
void main() {
print('Hello');
}
Top-level main is idiomatic. print is a core library function (not a static on System.out).
int, double, …) are not objects; wrappers exist.int/double/bool are types with fixed-size semantics but also objects in the unified type model; num is a supertype of int and double.Dart allows omission via line ending rules (Dart style often omits semicolons outside for heads etc.).
'...' and double "..." are both string literals; interpolation: '$name ${obj.field}'.r'\no escape'.'''...''' or """...""".String cannot hold null.String? can hold null.| Operator | Meaning |
|---|---|
?. |
null-aware member access |
?? |
null-coalesce |
??= |
assign if null |
! |
null assertion (“I assert not null”—avoid when possible) |
required |
named parameter must be provided (with null safety expectations) |
| Java | Dart |
|---|---|
@Nullable String |
String? |
Guava Optional<String> for presence |
Prefer String? or domain types; Optional-like patterns exist in packages but are not idiomatic core |
| NPE on dereference |
Compile-time help + runtime checks only when unsound escapes (dynamic, invalid !) |
dynamic vs Java Object
dynamic opts out of static checks (similar in spirit to very loose typing; unlike using Object with casts everywhere).Object? when you need “anything” but keep static checks.Java
void draw(int x) {}
void draw(int x, int y) {}
Dart — use named parameters, optional positional, or different names:
void draw(int x, [int? y]) { }
void drawNamed({required int x, int? y}) { }
void drawVertical(int x) { }
Named args are huge in Dart APIs (Flutter widget constructors are the famous example):
Widget build() => Padding(
padding: const EdgeInsets.all(8),
child: Text('Hi'),
);
Think: readable call sites > Java’s builder / telescoping constructors patterns.
int Function(int, int) vs Java’s functional interfaces (IntBinaryOperator, etc.).list.sort(compareTo) passes methods as values naturally.const constructors
Dart has true immutable const objects when types support const constructors — compile-time constants. Java’s final + static is related but not the same model.
class Singleton {
Singleton._();
static final Singleton instance = Singleton._();
}
Also idiomatic: factory constructors that may return cached instances or subtypes (think similar to static factory methods in Java EnumSet.of, List.of).
class Point {
final int x, y;
Point(this.x, this.y);
Point.origin() : this(0, 0);
}
Every class exposes an implicit interface of its instance methods. Java: implements requires explicit interface type. Dart: implements Foo where Foo is a class is normal.
Java: default interface methods + composition patterns. Dart: mixin + with apply reusable behavior without inheritance diamonds complexity of C++.
mixin Walks {
void walk() => print('walk');
}
class Person with Walks {}
Restrictions tightened with mixin class / class modifiers in Dart 3 — favor composition when modeling “capabilities.”
Java: preview/stable extension methods are newer and limited by platform. Dart: extension widely used (e.g. Flutter context.push style helpers in packages).
extension StringX on String {
String twice() => this + this;
}
Dart: List<String> is a subtype of List<Object> (if that still sounds wrong for a Java brain: in Dart’s sound variance rules for interface types, List is covariant in its type argument). That enables natural UI APIs but implies runtime checks on writes in some cases.
Java: at compile time List<String> is not a List<Object> for mutable lists (List<String> vs List<? extends Object> wildcards).
Practical advice: when porting Java mental models, re-read generic collection APIs and avoid unsound casts; trust the analyzer.
Java ? extends T, ? super T have no direct counterpart as a unified wildcard system; Dart uses upper bounds and ** typedefs / type parameters** differently. Many Java wildcard use cases become specific type parameters or helper methods.
enum Status {
ok(200),
notFound(404);
final int code;
const Status(this.code);
}
Java enums are strong too; Dart’s are now very expressive with fields and methods.
switch
Dart 3 sealed class hierarchies enable exhaustive switch—closer to algebraic data types in expression form.
Java 21 modern switch + sealed types — conceptual siblings; syntax differs.
record Point(int x, int y) {}
(int x, int y) record types — great for multiple returns without defining a full class.Core in dart:core: List, Map, Set, Iterable.
| Java | Dart |
|---|---|
ArrayList |
List (literal [1,2], growable vs fixed) |
HashMap |
Map (literal {'a':1}) |
HashSet |
Set |
Streams API (Stream<T> in java.util.stream) |
Two stream worlds: synchronous Iterable + async Stream<T> (see async section) |
Collection literals are idiomatic and compile to efficient code.
Java: checked IOException shapes API contracts. Dart: typically throws documentation + Future error channels — no throws clause enforced.
Error vs Exception
Dart has a loose convention:
Error: programmer mistakes / assertions / should fix code.Exception: recoverable failures.Neither is checked.
Future, Stream, async/await, isolates
CompletableFuture
Dart Future<T> is the baseline async result. async/await syntax maps closely to Java’s async/await if you use frameworks that add it, but in Dart it’s language-native.
Stream<T> is async sequences. Think RxJava-like operations exist in stream_transform, rxdart, but core Stream is smaller than Project Reactor.
Important: single subscription vs broadcast streams — learn early when wiring Flutter (e.g. UI listeners).
| Java | Dart |
|---|---|
| Threads share heap (with locks) | Isolates have separate heaps — no shared mutable state |
| Synchronization primitives |
message passing (SendPort/ReceivePort), Isolate.run patterns |
| Loom virtual threads | Isolates are not lightweight threads of the same sort |
Mental model: Flutter UI runs on main isolate event loop; heavy CPU work should move to workers (compute, isolates) to avoid jank.
part, export, packages
A .dart file is a library (optionally named).
part / part of
Splits one library across files (often for generated code: *.g.dart with build_runner).
export
Barrel files re-export other libraries (similar to Java aggregator patterns but simpler).
pubspec.yaml)
Think pom.xml / build.gradle + coordinates, but much leaner:
Version solving is declarative (pub resolver).
Private Dart members: leading underscore _detail is library-private (not private keyword).
| Need | Java common | Dart |
|---|---|---|
| Build | Maven / Gradle |
dart pub get, dart run, dart compile
|
| Annotation processing / codegen |
javac -processor, Gradle annotationProcessor
|
dart run build_runner build with build / source_gen generators — no built-in “APT” keyword; this is the usual Java annotation-processing substitute |
| Format | Spotless / Checkstyle |
dart format (often CI-enforced) |
| Lint | Checkstyle / Error Prone |
analysis_options.yaml + package:lints / flutter_lints
|
| Test | JUnit 5, AssertJ |
package:test, flutter_test
|
| Coverage | Jacoco | SDK / package coverage |
The Dart analyzer powers IDE experience (similar spirit to IntelliJ/Java LS but often remarkably fast for incremental analysis).
| Mode | What it means for a Java dev |
|---|---|
| JIT VM (dev) | Fast reload (Flutter hot reload changes UI state carefully) |
| AOT native | Release builds without shipping a VM bytecode interpreter (platform-dependent) |
| dart2js / Wasm | Web targets — no JVM |
GraalVM native image is the rough Java-side analogy for AOT, but pipeline details differ a lot.
Flutter is not a language change—same Dart language. What changes is:
Widget trees).provider, riverpod, bloc, get_it, …) — maps to Spring-ish DI only by analogy.Hot reload is a killer feature for UI iteration; Java UI historically relied on slower full restarts (with exceptions in some frameworks).
Minimal shelf middleware model — like a small servlet filter chain or Ring-style handler stacks.
Relic (from the Serverpod team) is a modern HTTP stack positioned as a next-gen basis with strong typing, routing, WebSockets, static files—Serverpod builds on it. If you see “Relic” in 2026-era Dart docs, read it as framework plumbing, not archaeology.
Serverpod aims at full-stack Dart: server ORM, migrations, tooling, code generation, integrated auth patterns—closest spirit to Rails/Django/Laravel (still maturing vs Spring’s 20-year ecosystem).
Lightweight server from Very Good Ventures—good for APIs and BFF layers.
Historical note: Aqueduct was popular years ago; check current maintenance before new projects—ecosystem moves fast.
Official grpc Dart package exists—good interop with Java microservices.
This is not exhaustive—pub.dev has massive breadth. Treat as signposts.
| Area | Packages / notes |
|---|---|
| HTTP client |
http, dio (interceptors, richer client features) |
| JSON serialization |
json_serializable + build_runner, freezed immutable unions |
| Routing (Flutter) |
go_router, auto_route, … |
| State (Flutter) |
provider, riverpod, bloc, … |
| Functional helpers |
dartz (Either, Task): Haskell-flavored—love/hate |
| Logging |
logging package standard interfaces |
| Env/config |
flutter_dotenv, custom patterns |
| Tests mocking |
mockito, mocktail
|
| Concurrency helpers |
pool, isolate wrappers |
| DB (Dart server) |
postgres, mysql drivers; ORM layers vary by framework |
| Redis etc. | community packages—verify maintenance |
Serverpod bundles many server concerns opinionated.
| Feature | Java | Dart |
|---|---|---|
| Primitive types | Yes | Unified object model (int, double, …) |
| Checked exceptions | Yes | No |
| Method overloading | Yes | No |
static class file plumbing |
import static |
Top-level functions idiomatic |
| Wildcard generics |
? extends, ? super
|
Different variance model |
| Protected visibility | protected |
No—library privacy with _
|
| Package-private | Yes | Library scope via _ + library boundaries |
| Multiple inheritance of classes | No | No—but mixins and mixin class patterns |
| Interface default methods | Yes | Default instance methods on classes; extension
|
enum power |
Strong since Java 5; evolved | Enhanced enums great |
switch exhaustiveness |
Improved in modern Java |
sealed + patterns strong |
record |
Java 16+ | Dart 3 records |
| Null safety in type system | Modern Java + annotations | ? types + soundness |
| Threads | Rich | Isolates + async event loop |
| VM / native / web | JVM primary | VM, AOT, JS, Wasm |
| Mobile UI | Android (Kotlin often) | Flutter dominant Dart UI |
Java strengths not matched 1:1 in Dart
dart:ffi for C interop instead).Dart strengths / conveniences
pub, formatter, analyzer).T?, not null everywhere + comments.async/await—avoid callback pyramids like avoiding raw CompletableFuture chains in modern Java.List/Map variance rules before performance-critical numeric code with heavy generics.package:lints / flutter_lints early—Dart’s analyzer is your pair programmer.final fields, freezed where helpful) especially in Flutter UIs.| Java concept | Dart counterpart / note |
|---|---|
package com.foo.bar |
package:foo_bar/foo_bar.dart + lib/ layout; import URI not a 1:1 filesystem mirror |
public |
Symbols are public to importing libraries unless _ private
|
private |
_leadingUnderscore = library-private (not class-private) |
protected |
No direct equivalent — prefer small public APIs, @visibleForTesting, or composition |
static nested class |
Often top-level private class in same library |
| Interface |
abstract class, interface class (Dart 3), or “implements a class’s interface” |
| SAM / functional interface |
Function types (int Function(int a)) |
static final constant |
static const, const constructors, top-level const
|
Reflection (java.lang.reflect) |
dart:mirrors on VM only — Flutter release/AOT avoided; use codegen
|
Annotation processing (javac -processor, Gradle annotationProcessor) |
build_runner + packages like source_gen — no separate JVM-style APT in the language; this is the default industrial substitute |
| JNI |
dart:ffi for C interop (plus package:ffi helpers) |
synchronized |
Prefer atomic single-isolate async patterns; isolates + message passing for parallelism |
volatile / Java memory model |
No shared mutable memory between isolates — immutable messages |
ThreadLocal |
Zone, Flutter InheritedWidget, or explicit parameter passing |
Module (module-info.java) |
package boundaries + exports; no JPMS verifier |
synchronized, strictfp, native, transient, volatile, throws as a contract keyword.mixin, extension, on (extension / mixin constraints), with (mixin application), factory, operator, external, covariant, yield, yield* (generator forms), late, required, async, await, dynamic, library, part, export, show, hide, deferred (deferred loading).interface (Dart 3 class modifier, not Java’s interface keyword alone), super (mixins / super parameters), new (optional in Dart).For every predefined Dart operator token, precedence row, and Java analogue, see Appendix — Dart operators — complete predefined set (Java notes) (aligned with dart.dev/language/operators).
| Dart | Rough Java analog | Notes |
|---|---|---|
?. |
conditional navigation | null-aware member access |
.. / ?..
|
no single operator | Cascade: perform multiple ops on same receiver |
~/ |
integer division | truncating division for num
|
as / is / is!
|
cast / instanceof
|
prefer patterns when possible |
?? / ??=
|
null checks + assignment helpers | idiomatic defaults |
Postfix ! |
(Type)expr you hope is safe |
runtime failure if wrong |
=> |
lambda / concise body | Arrow bodies everywhere |
... spread |
List.copyOf, streams combine |
collection / map spreads in literals |
dart:* libraries — Java mental anchors
| Library | Purpose | Java anchor |
|---|---|---|
dart:core |
Core types, collections, Uri, RegExp, DateTime, errors |
java.lang + parts of java.util
|
dart:async |
Future, Stream, StreamController, zones |
CompletableFuture, reactive Publisher (not identical) |
dart:collection |
queues, linked maps, etc. |
java.util extras |
dart:convert |
JSON (JsonCodec), UTF-8, base64 |
Jackson spirit (not drop-in) |
dart:io |
VM & native I/O, HTTP server, sockets, File, Process
|
NIO + java.net + java.io
|
dart:html |
Browser DOM (transpiled) | JS / not JVM |
dart:js_interop |
JS interop (modern) | JNI for JS land |
dart:ffi |
C ABI interop | Panama / JNI (different) |
dart:isolate |
isolate spawn, ports | threads + message queues |
dart:math |
sqrt, Random, min, max
|
Math, Random
|
dart:typed_data |
Uint8List, views, ByteData
|
ByteBuffer + primitive arrays |
dart:developer |
timeline, logging, service extensions | JFR / JVMTI (much smaller surface) |
| Modifier | Intent |
|---|---|
final class |
Prevents subtyping outside the library (library-first design). |
sealed class |
Permitted subtypes only in same library — enables exhaustive switch. |
interface class |
Direct instantiation restricted — implement elsewhere. Controls construction, not just “interface-ness.” |
mixin class |
Declares something usable as mixin and as class. |
Java sealed uses permits and named subclasses anywhere in allowed modules. Dart sealed subtypes sit in the same library — different packaging tradeoff.
switch expressions (Dart 3)
Switch expressions return values; pattern cases match structures (records, object shapes):
sealed class Msg {}
class Ping extends Msg {}
class Pong extends Msg {}
String react(Msg m) => switch (m) {
Ping() => 'ping',
Pong() => 'pong',
};
void tupleDemo() {
final (x, y) = (1, 2);
print('$x $y');
}
Think Java’s modern switch + deconstruction converging—Dart 3 doubles down on switch as expression and algebraic shapes via sealed hierarchies + records.
call: an object can behave like a function:
class DoubleFn {
int call(int x) => x * 2;
}
Operators: implement operator + etc. Java lacks user-defined + for arbitrary classes.
typedef and type aliases
typedef JsonMap = Map<String, Object?>;
typedef IntPredicate = bool Function(int value);
Closest Java analogue: type parameter bounds + functional interfaces, not a first-class typedef.
intl)
DateTime handles instant vs local/utc flags; time zones usually need timezone package or careful UTC storage.package:intl for DateFormat, messages, plural rules — pair with Flutter localization (MaterialApp delegates).Java ZonedDateTime remains richer for some server use cases.
RegExp lives in dart:core.Pattern—always add tests for tricky inputs.Uri.parse / Uri.https over string concatenation—similar discipline as java.net.URI.http (minimal) vs dio (interceptors, cancel tokens, multipart ergonomics).typed_data vs NIO
| Dart | Java |
|---|---|
Uint8List |
byte[], ByteBuffer
|
ByteData |
primitive get/put on ByteBuffer
|
Endian |
ByteOrder |
Isolates copy messages; no shared **ByteBuffer between threads** like Java.
dart:mirrors exists for VM scenarios; Flutter iOS/Android release builds do not support mirrors in typical configurations (tree shaking + AOT).build_runner ≈ “annotation processing” culture: The JVM has compile-time annotation processors (APT) wired through javac / Gradle / Maven (-processor, annotationProcessor dependencies). Dart has no separate javac-style processor hook in the language itself—instead, packages such as build, source_gen, and build_runner implement a declarative, file-generating build graph. You put dev_dependency: build_runner and dev_dependency: <generator> in pubspec.yaml, annotate code (e.g. @JsonSerializable()), then run dart run build_runner build (or watch). That workflow is the standard replacement for Java’s Lombok / MapStruct / Dagger / Room–style codegen when you need *.g.dart, *.freezed.dart, mocks, serializers, etc.
Practical rule: if you type import 'dart:mirrors' in app code, stop—reach for build_runner-driven generators (json_serializable, freezed, drift, injectable, mockito codegen, …), the same way you’d reach for annotation processors in a Java service.
Built-ins include @override, @Deprecated. Create const annotation classes — runtime reflection is limited compared to Java’s Annotation reflection APIs; codegen fills the gap. Custom annotations in Dart are often inputs to build_runner generators (same role as Java annotation processor arguments), not something you reflectively query at runtime like getDeclaredAnnotations() on the JVM.
| Approach | Dart |
|---|---|
| Schema-first codegen |
json_serializable, OpenAPI generators in ecosystem |
| Algebraic / union types |
freezed (sum types + copyWith) |
| Hand-written | OK for tiny DTOs |
| Package | Sketch | Caveat |
|---|---|---|
| sqflite | SQLite on some platforms | Not web |
| drift | Typed SQL + migrations + codegen | Strong for structured data |
| isar | Fast embedded NoSQL | Check platform matrix |
| hive | Key-value boxes | Simplicity vs relational features |
Server-side Postgres often pairs with postgres driver + framework migrations (e.g. Serverpod).
| Java mindset | Dart / Flutter common |
|---|---|
| Spring singleton beans |
get_it lazy singletons |
| Constructor injection everywhere | Possible; many Flutter apps mix constructor + provider |
| Request scope |
Provider/riverpod overrides scoped to widget subtree |
Riverpod ≈ “recompute graph + caching + test overrides”. Not a servlet container.
| Concern | Java | Dart |
|---|---|---|
| Declared deps | Maven / Gradle | pubspec.yaml |
| Lockfile | Gradle locks |
pubspec.lock (commit in apps, not always in pure libs) |
| Build plugins / codegen | Gradle annotationProcessor, maven-compiler-plugin processor path |
build_runner + generator packages (build, source_gen); no separate “APT” concept—tasks are dart run build_runner ...
|
| Multi-package repos | composite builds |
melos mono-repo helper |
Note: Java annotation processing is a compiler plugin convention; Dart’s parallel is build_runner orchestrating builder packages—not a keyword in the language, but the default industrial approach to generated sources.
package:test, group/setUp/tearDown feel like JUnit rules-lite.flutter_test + WidgetTester (think UI unit with fake async).integration_test driving real devices/emulators.Mocks: mockito (codegen) or mocktail (lambda-friendly).
| Area | Packages (examples) |
|---|---|
| Routing |
go_router, auto_route, beamer
|
| Networking |
http, dio, retrofit
|
| Serialization |
json_serializable, freezed
|
| Immutability helpers |
freezed, equatable
|
| Assets / env | flutter_dotenv |
| Firebase |
firebase_core, firebase_auth, cloud_firestore, … |
| Crash / perf |
sentry, firebase_crashlytics
|
| Images |
cached_network_image, flutter_svg
|
| Codegen infra |
build, build_runner, source_gen
|
| Lint |
lints, flutter_lints, very_good_analysis
|
| Monorepo | melos |
Always verify platform support (iOS/Android/Web/Desktop/Server) before you commit.
| Topic | Java | Dart | Practical delta |
|---|---|---|---|
| Primitive vs object types | primitives + wrappers | unified num/int/double/bool
|
fewer autoboxing surprises; int VM 64-bit, BigInt for bigger; JS has 53-bit safe int caveats |
| Fixed arrays | String[] |
List<String> |
growable lists are default; List.filled etc. for fixed |
| Wildcards at call sites |
? extends T puzzles |
different generic puzzles | Read Dart variance docs before “clever” APIs |
| Checked exceptions | throws IOException |
none | encode failures in Result types (dartz), Either, or typed exceptions by convention |
protected |
yes | no | design smaller libraries + _ private helpers |
| Inner classes | common | supported, less common | prefer top-level private classes |
| Static import | import static |
import '...' show foo; |
top-level functions feel “static-like” |
enum with data |
recent Java upgrades | enhanced enums (fields, methods) | both strong—syntax differs |
switch exhaustiveness |
sealed helps Java |
sealed in library helps Dart |
library locality differs |
| Reflection | core to many frameworks | VM-only mirrors | codegen culture |
| Web as target | not JVM | dart2js / wasm |
conditional imports for dart:html vs dart:io
|
| Money / decimals | BigDecimal |
packages (decimal, etc.) |
plan precision early |
avoid!)
| Escape hatch | Risk |
|---|---|
dynamic |
disables static checks — like Object + unchecked casts everywhere |
Postfix ! |
NullPointerException-class failures when wrong |
covariant |
advanced param substitution — reserve for framework authors |
dart:mirrors in Flutter |
broken / disallowed in release paths |
? types and Flutter’s widget rules are their own discipline; don’t assume Kotlin === Dart.Dart has lazy iterable generators and async* streams using yield / yield*:
| Form | Returns | Java intuition |
|---|---|---|
Iterable<T> sync*() sync* |
lazy sequence | custom Iterator with state machine |
Stream<T> async*() async* |
async sequence |
Publisher / reactive patterns (not identical) |
yield* delegates to another iterable/stream—useful for composing tree walks or parsers.
for-in vs Java
Iterable<E> in Dart is like Java’s Iterable<T> — provides Iterator<E> iterator.for (final x in xs) is sugar over iterator.moveNext().Iterable (map, where, fold…) resemble Stream API ergonomics but Iterable is synchronous.Key distinction: Java Stream is often one-shot; Dart Iterable can be re-iterated if the underlying iterable supports it (and some are single-use like Iterator patterns—read docs per concrete type).
import 'heavy.dart' deferred as heavy; — loads library later; await heavy.loadLibrary() before use. Roughly dynamic plugin loading (not OS .so like JNI—Dart bundle loading).
Split VM vs web implementations:
import 'stub.dart'
if (dart.library.io) 'io_impl.dart'
if (dart.library.html) 'html_impl.dart';
Use when platform APIs diverge (dart:io vs dart:html)—think separate *-jvm / *-android artifacts in Maven but resolved at compile time per target.
dart:ffi vs JNI (sketch)
| Concern | JNI (Java) | dart:ffi |
|---|---|---|
| Interop target | JVM ↔ native (often C/C++) | Dart VM ↔ C ABI |
| Memory |
GetPrimitiveArrayCritical, pinning |
Pointer, calloc, Struct packing |
| Async | blocking calls off critical path | isolate-friendly; don’t block UI |
| Flutter | JNI / platform channels | FFI for perf-sensitive native; platform channels for Android/iOS SDK |
Panama in modern Java is spiritually closer ergonomically than legacy JNI, but Dart dart:ffi is still its own API surface.
Dart allows forwarding constructor parameters to super constructors compactly (super parameters), and : initializer lists run before constructor bodies—useful for final field assignment from computed values.
Think telescoping constructors in Java but with less boilerplate.
Three trees (simplified mental model):
Widget — immutable configuration (“do I want padding?”). Compare to a declarative description.Element — life cycle of who owns State; holds reference between frames.RenderObject — layout & paint; mutated during layout.Java UI frameworks (Swing) historically emphasized mutable component graphs more; Flutter’s setState / rebuild reconciles new widget descriptions against existing elements/render objects.
int is a 64-bit integer (overflow wraps / is defined per operation—see language spec); use BigInt for arbitrary-precision integer math (Java BigInteger analogue).BigInt patterns appropriate for JS compilation—do not assume Java long-range arithmetic portably without checks.Treat numeric code that must run on Flutter Web and mobile as a cross-platform concern: tests on both targets.
Paired Java vs Dart examples. Prefer sound Dart 3 / null-safe snippets; remove ? only where the teaching point is nullable types.
~/, cascades, map).Java
public class Demo {
public static int answer = 42;
public static void main(String[] args) {
System.out.println(answer + " " + args.length);
}
}
Dart — top-level main, variables, functions (no class wrapper required):
int answer = 42;
void main(List<String> args) {
print('$answer ${args.length}');
}
Java — //, /* */, /** */ Javadoc.
Dart — same + /// doc comments drive dartdoc (like Javadoc for API extraction).
/// One-line doc.
// runtime comment
/*
block
*/
var, final, const, late
Java
final String name = "Ann";
var count = 1; // Java 10+
Dart
var count = 1; // inferred as int; cannot later assign bool
final name = 'Ann'; // single assignment, inferred String
const pi = 3.14; // compile-time constant
late String description; // non-nullable, assigned before use
?) — Kotlin-like, not Java primitives
Java (modern style / annotations)
@Nullable String nick;
String s = nick; // still needs null check for safety
Dart
String? nick;
String s = nick!; // bang — avoid; prefer ?. ?? or flow checks
Full list: Appendix — Dart operators (complete predefined set, Java notes)
Java
int a = 7 / 3; // 2 (int division)
double x = 7 / 3.0;
Dart
var q = 7 / 3; // 2.5 — because / always promotes to double when needed
var t = 7 ~/ 3; // 2 — truncating integer division (Learn X in Y Minutes)
Java — increment
for (int i = 0; i < n; i++) {}
Dart
for (var i = 0; i < n; i++) {}
Dart only (style from null-aware history — see also Learn X in Y Minutes):
String? name;
var len = name?.length;
var display = name ?? 'guest';
name ??= 'anon';
cascadeExample() {
final sb = StringBuffer()
..write('a')
..write('b'); // cascade: same receiver
}
Java (verbose analog)
String name = null;
Integer len = name != null ? name.length() : null;
Java
String msg = "Hello, " + name + "!";
var block = """
line1
line2
""";
Dart
final msg = 'Hello, $name!'; // or ${expr}
final block = '''
line1
line2
''';
final raw = r'\n stays literal';
if / ternary
Java if (cond) {} else {}, cond ? a : b
Dart identical shape; condition must be bool (no implicit truthiness from arbitrary objects).
for, enhanced for, forEach
Java
for (int i = 0; i < xs.size(); i++) { var x = xs.get(i); }
for (var x : xs) {}
xs.forEach(x -> System.out.println(x));
Dart (codelab functional style)
for (var i = 0; i < xs.length; i++) {
final x = xs[i];
}
for (final x in xs) {}
xs.forEach(print);
// or: xs.map(scream).forEach(print);
while / do-while
Same structure in both languages.
switch — classic vs Dart 3 patterns
Java (pre–pattern matching style)
switch (k) {
case 1:
System.out.println("one");
break;
default:
break;
}
Dart — still supports break in statement switches; Dart 3 adds expression switch with patterns (no break between cases in expression form):
String label(int k) => switch (k) {
1 => 'one',
_ => 'other',
};
Dart — classic statement switch (still used; Learn X in Y Minutes style — each case falls through only with continue labels; normally end with break or return):
void demo(int v) {
switch (v) {
case 1:
print('one');
break;
default:
print('other');
}
}
Java — checked + unchecked.
try {
throw new IOException("x");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
System.out.println("done");
}
Dart — no checked exceptions; throw any object.
try {
throw StateError('x');
} on StateError catch (e) {
rethrow;
} catch (e, st) {
// stack trace
} finally {
print('done');
}
this constructor, no public keyword
Java Tutorial Bicycle style (multiple getters / setters)
public class Bicycle {
private int cadence;
private int speed = 0;
private int gear;
public Bicycle(int cadence, int speed, int gear) {
this.cadence = cadence;
this.speed = speed;
this.gear = gear;
}
}
Dart codelab — shorthand + _ privacy (library scope)
class Bicycle {
int cadence;
int _speed = 0;
int get speed => _speed;
int gear;
Bicycle(this.cadence, this.gear); // speed internal
void applyBrake(int decrement) => _speed -= decrement;
}
Java — overload constructors
public Rectangle() { this(0,0,0,0); }
public Rectangle(int w, int h) { this(0,0,w,h); }
Dart — single constructor with named optional + defaults (codelab Rectangle)
class Point {
final int x, y;
const Point(this.x, this.y);
}
class Rectangle {
Point origin;
int width, height;
Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});
}
factory constructor (codelab)
Option A — Dart top-level
Shape shapeFactory(String type) {
return switch (type) {
'circle' => Circle(2),
'square' => Square(2),
_ => throw ArgumentError(type),
};
}
Option B — factory on type
abstract class Shape {
factory Shape(String type) {
return switch (type) {
'circle' => Circle(2),
'square' => Square(2),
_ => throw ArgumentError(type),
};
}
num get area;
}
Java analog is often static factory methods (Boolean.valueOf, etc.).
implements every class interface — CircleMock pattern (codelab)
Dart
abstract class Report {
String build();
}
class HtmlReport implements Report {
@override
String build() => '<html/>';
}
Java
interface Report { String build(); }
class HtmlReport implements Report {
@Override public String build() { return "<html/>"; }
}
super
Java
class Child extends Base {
Child(int x) { super(x); }
}
Dart
class Child extends Base {
Child(super.x);
}
with M1, M2)
Java has single inheritance; default interface methods approximate some sharing.
Dart — mixins compose left-to-right; if two mixins supply the same member name, resolution follows Dart’s linearization (document the exact app in language spec when it matters for overrides).
class Vehicle {}
mixin Engine on Vehicle {
String sound() => 'vroom';
}
mixin Horn on Vehicle {
String beep() => 'beep';
}
class Car extends Vehicle with Engine, Horn {}
void main() {
final c = Car();
print(c.sound());
print(c.beep());
}
Design takeaway: put the “most specific” mixin last when you layer overrides—same discipline as multiple interface defaults in Java, but with mixin + super semantics.
Dart
extension StrX on String {
String twice() => this + this;
}
Java — no extension methods until recent preview features; historically static helpers.
operator overloading & call
Dart
class Vec2 {
final int x, y;
const Vec2(this.x, this.y);
Vec2 operator +(Vec2 o) => Vec2(x + o.x, y + o.y);
}
class Multiplier {
final int k;
const Multiplier(this.k);
int call(int n) => k * n; // callable object
}
Java — no operator+; use plus(Vec2 o).
Java (erasure at runtime)
List<String> xs = new ArrayList<>();
// xs.getClass() is List, not List<String>
Dart
void main() {
final xs = <int>[1, 2];
print(xs.runtimeType); // List<int> — type argument preserved (reified)
}
async / await / Future / Stream
Java (CompletableFuture)
CompletableFuture.supplyAsync(() -> 1)
.thenApply(x -> x + 1)
.join();
Dart
Future<int> load() async {
final x = await Future.value(1);
return x + 1;
}
sync* / async*
Dart (Learn X in Y Minutes style)
Iterable<int> countUp(int n) sync* {
for (var i = 0; i < n; i++) {
yield i;
}
}
Stream<int> tick() async* {
for (var i = 0; i < 3; i++) {
await Future.delayed(Duration(milliseconds: 100));
yield i;
}
}
Java — no direct language feature; use Stream.iterate / reactive APIs.
Dart
import 'dart:isolate';
Future<int> heavy(int n) async {
return Isolate.run(() {
// no shared mutable heap with spawner — message-passing / copying
var s = 0;
for (var i = 0; i < n; i++) s += i;
return s;
});
}
typedef & function types
Dart
typedef IntPred = bool Function(int x);
typedef StringMap = Map<String, Object?>;
bool positive(int x) => x > 0;
void main() {
final IntPred p = positive;
print(p(1));
}
Java — IntPredicate, Predicate<Integer>, etc.
dart: / relative / package: / show / hide / as / deferred
Dart
import 'dart:convert';
import 'dart:math' as math;
import 'package:path/path.dart' show join;
import 'helper.dart' hide Secret;
import 'slow.dart' deferred as slow;
Future<void> later() async {
await slow.loadLibrary();
slow.doWork();
}
Java — import static, module path; no deferred import analog in the language proper.
enum (Dart 2.17+)
Dart
enum HttpCode {
ok(200),
notFound(404);
final int code;
const HttpCode(this.code);
}
Java — enums strong since Java 5; enhanced with fields/methods similarly.
record
Java 21-style
record Point(int x, int y) {}
Dart 3
void main() {
final r = (1, 2); // Record type
final (a, b) = r; // destructure
print('$a $b');
}
No single Java built-in; think Lombok + Jackson patterns. freezed generates immutable data classes, copyWith, JSON serialization (with json_serializable), and union “sealed” types—add build_runner and run:
dart run build_runner build --delete-conflicting-outputs
See freezed README for the exact annotation syntax (it evolves between major versions).
assert / debug* compilation
Dart
void check(int x) {
assert(x >= 0, 'must be non-negative');
}
Stripped in release Flutter/AOT builds by default for assert.
Dart — “everything is a builder” (mutable configuration fluently)
void fill(StringBuffer sb) {
sb
..write('a')
..write('b');
}
Java — typically StringBuilder chained calls return this manually.
Official reference and precedence: Operators | dart.dev. Many symbols can also be implemented as class members (user-defined operator), similar in spirit to C++/C#—Java does not allow user-defined +, [], etc.
Precedence (highest → lowest) — reproduced from the language docs; each row is higher than rows below it.
| Description (Dart) | Operators | Associativity |
|---|---|---|
| Unary postfix |
++ -- () [] ?[] . ?. !
|
None |
| Unary prefix |
-expr !expr ~expr ++expr --expr await expr
|
None |
| Multiplicative |
* / % ~/
|
Left |
| Additive |
+ -
|
Left |
| Shift |
<< >> >>>
|
Left |
| Bitwise AND | & |
Left |
| Bitwise XOR | ^ |
Left |
| Bitwise OR | `
|
` |
| Relational & type test |
>= > <= < as is is!
|
None |
| Equality |
== !=
|
None |
| Logical AND | && |
Left |
| Logical OR | `
|
|
| If-null | {% raw %}??
|
Left |
| Conditional | expr1 ? expr2 : expr3 |
Right |
| Cascade |
.. ?..
|
Left |
| Assignment |
= *= /= += -= &= ^= `
|
= %= ~/= <<= >>= >>>= ??=` |
| Spread (see note) |
... ...?
|
(not an expression operator; see below) |
Note (spread): Per dart.dev, ... / ...? are part of collection / map literals, not ordinary binary operators; “precedence” is best read as lowest in the sense that the spread operand can be any expression.
| Dart | Role | Java analogue / comment |
|---|---|---|
() |
Call / function application |
f(a) — same; Dart supports optional/named actual args differently |
[] |
Index / subscript operator | Arrays: a[i]. List/Map: no [] on java.util.List—use get / Map#get; Dart list[i] / map[k] is operator syntax |
?[] |
Null-aware index | No single operator; list == null ? null : list[i]
|
. |
Member access |
. — same |
?. |
Null-aware member | No ?. in Java; verbose null checks or Optional
|
! (postfix) |
Not-null assertion (expr!) |
Objects.requireNonNull(expr) then use; wrong ! → runtime error (like failed cast) |
++ -- (prefix/postfix) |
Increment / decrement | Same; only on var / fields that are mutable (late / assignable) |
- (unary) |
Negation |
- — same |
! (prefix) |
Boolean NOT |
! — Java requires boolean; Dart ! expects bool in sound code |
~ (unary) |
Bitwise NOT |
~ — same for integrals |
await |
Suspend until Future completes |
CompletableFuture/virtual threads—not a unary keyword in Java the same way |
* / % |
Multiply, divide, remainder | Same; / on Dart int promotes to double where needed—unlike Java int/int
|
~/ |
Integer (truncating) division |
Math.floorDiv (Java 18+) or a / b for ints (trunc toward zero)—behavior differs from floorDiv for negatives; know your contract |
+ - (binary) |
Add / subtract | Same; + also string concat in both (prefer Dart interpolation) |
<< >> >>> |
Shifts | Java has >>>; Dart >>> is unsigned right shift (mind web 32-bit masking per docs) |
**& ^ ` |
`** | Bitwise and / xor / or |
> < >= <= |
Relational | Same |
as |
Cast |
(Type) e — Dart as throws if wrong type (also, import 'x.dart' as p uses as for a library prefix, not a cast) |
is is! |
Type test / negated test |
e instanceof T / !(e instanceof T)
|
== != |
Equality / inequality | Dart == is first.==(second) with null shortcut rules; Java == is reference compare for objects—use equals for value equality |
**&& ` |
`** | |
?? |
If-null |
Optional.ofNullable(a).orElse(b) / ternary—no ?? in Java |
??= |
Assign if null | No direct; if (x == null) x = v
|
? : |
Conditional | Same ternary |
.. ?.. |
Cascade | No cascade—repeat receiver or builder pattern |
= |
Assignment | Same |
*= /= += -= %= |
Compound assign | Same |
~/= |
Compound trunc-div | No single operator; a = Math.floorDiv(a, b) or /= for ints |
**<<= >>= >>>= &= ^= ` |
=`** | Compound bitwise/shift |
... |
Spread into another collection/map literal | Java varargs / List.copyOf / Stream.concat—no literal spread until you build lists |
...? |
Null-aware spread | Same as spread after null filter |
operator … in a class |
User-defined +, -, [], … |
Java has no user-defined operators (always methods like plus, get) |
// ~/ vs /
assert(5 / 2 == 2.5);
assert(5 ~/ 2 == 2);
// == calls Object== / overridden ==
final s1 = 'a';
final s2 = String.fromCharCode(97);
assert(s1 == s2); // true — content equality for String
// is / as
Object o = 1;
if (o is int) print(o + 1); // promoted to int
final x = o as int;
// Null-aware and assert-non-null
String? n;
print(n?.length);
print(n!.length); // runtime if n is null
// Cascade (not nested-dot default)
final sb = StringBuffer()..write('a')..write('b');
// Spread in literal
final inner = [2, 3];
List<int>? tail;
final outer = [1, ...inner, if (true) 4, ...?tail];
Java (contrast for == on reference types):
String a = new String("x");
String b = new String("x");
System.out.println(a == b); // false — identity
System.out.println(a.equals(b)); // true — value
End of appendix snippets — see main chapters for variance, isolates detail, and tooling.