| theme | background | class | highlighter | lineNumbers | info | drawings | transition | title | mdc | |
|---|---|---|---|---|---|---|---|---|---|---|
seriph |
text-center |
shiki |
false |
## Java Tier List
By Kenneth Kousen
Learn more at [KouseniT](https://kousenit.com)
|
|
slide-left |
Java Tier List |
true |
Ken Kousen
Kousen IT, Inc.
- ken.kousen@kousenit.com
- http://www.kousenit.com
- http://kousenit.org (blog)
- Social Media:
- @kenkousen (Twitter)
- @kousenit.com (Bluesky)
- https://www.linkedin.com/in/kenkousen/ (LinkedIn)
- Tales from the jar side (free newsletter)
- Source code: https://github.com/kousen/java_latest
This presentation is based on the detailed tier list breakdown in the video above
- Completely transformed Java programming
- Game changer for collections processing
- Enabled functional programming paradigms
- Industry-wide adoption
// Lambda expressions
names.forEach(name -> System.out.println(name));
// Method references
names.forEach(System.out::println);
// Streams API
List<Integer> evens = IntStream.range(1, 100)
.filter(n -> n % 2 == 0)
.boxed()
.collect(Collectors.toList());- Solved interface evolution problem
- Enables backward compatibility
- Makes interfaces more flexible
- Critical for API designers
public interface Calculator {
// Default method
default double sqrt(double x) {
return Math.sqrt(x);
}
// Static method
static double add(double a, double b) {
return a + b;
}
}
class BasicCalc implements Calculator {
// Inherits sqrt() implementation
}- Good concept, mixed reception
- Makes null handling explicit
- Often misused (Optional parameters, fields)
- Better than null, but adds verbosity
// Optional way - explicit about nullability
Optional<String> optional = Optional.ofNullable(getValue());
optional.map(String::toUpperCase)
.ifPresent(System.out::println);
// Return type clarity
public Optional<User> findUserById(Long id) {
return Optional.ofNullable(userRepository.find(id));
}- Massive quality of life improvement
- Immutable by default (safer)
- Concise syntax
- No external libraries needed
// Concise and immutable
List<String> langs = List.of("Java", "Python", "Kotlin");
Set<Integer> nums = Set.of(1, 2, 3, 4, 5);
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 87);
// For many entries
Map<String, String> config = Map.ofEntries(
Map.entry("host", "localhost"),
Map.entry("port", "8080")
);Full examples: collections/FactoryMethodDemo.java
- Reduces boilerplate nicely
- Still strongly typed (not like JavaScript!)
- Great for complex generic types
- Some debate on readability
// With var - cleaner, still strongly typed
var userScores = new HashMap<String, List<Integer>>();
var outputStream = new ByteArrayOutputStream();
var name = "Java"; // String
var count = 42; // int
// Works great with streams
var result = list.stream()
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.collect(Collectors.toList());- Modern replacement for HttpURLConnection
- Built-in HTTP/2 support
- Both sync and async APIs
- No more Apache HttpClient dependency
// Modern, fluent API
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/users/octocat"))
.header("Accept", "application/json")
.GET()
.build();
// Both sync and async supported
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());Full examples: http/AstroClient.java, http/JokeClient.java
- Great for learning and prototyping
- Perfect for conference demos
- Useful for testing small code snippets
- Not widely adopted in practice
// Interactive Java REPL
jshell> int x = 10
x ==> 10
jshell> x * x
$2 ==> 100
jshell> List.of("Alice", "Bob")
$3 ==> [Alice, Bob]
jshell> /methods
| int square(int)- Great for scripts and learning
- Lowers barrier to entry
- Perfect for small utilities
- Makes Java competitive with Python/Ruby for scripts
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, " + args[0] + "!");
}
}# Direct execution - no compilation step!
java Hello.java World
# Output: Hello, World!- Finally makes switch useful!
- No fall-through bugs
- Can be an expression (returns value)
- Multiple case labels
// New switch expression - concise and safe
String result = switch (day) {
case MONDAY, FRIDAY -> "Work day";
case SATURDAY, SUNDAY -> "Weekend";
default -> "Midweek";
};Full examples: enhancedswitch/DaysInMonth.java
- Massive quality of life improvement
- Perfect for JSON, SQL, HTML
- No more escape sequences
- Preserves formatting
// Text blocks - clean and readable
String json = """
{
"name": "Alice",
"age": 30
}
""";
String sql = """
SELECT id, name, email
FROM users
WHERE active = true
""";Full examples: textblocks/TextBlocks.java
- Eliminates data class boilerplate
- Immutable by default
- Pattern matching friendly
- Perfect for DTOs and value objects
// Record - one line!
public record Point(int x, int y) {}
// Usage
var p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]Full examples: records/Person.java, records/Product.java
- No more explicit casting
- Variable automatically typed and scoped
- Reduces boilerplate significantly
- Foundation for more pattern features
// Old way - verbose and error-prone
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
// Pattern matching - clean and safe
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}Full examples: patternmatching/UseShapes.java
- Works with complex conditions
- Variable scope limited to true branch
- Combines type checking with logic
- Eliminates common casting bugs
// Complex conditions with pattern variable
if (obj instanceof String s && s.length() > 5) {
System.out.println("Long string: " + s);
}
// Multiple checks in same method
if (obj instanceof Integer i && i > 0) {
System.out.println("Positive integer: " + i);
} else if (obj instanceof String s && !s.isEmpty()) {
System.out.println("Non-empty string: " + s);
}Full examples: patternmatching/UseShapes.java
- Type patterns in switch expressions
- Guarded patterns with
when - More flexible than traditional switch
- Eliminates casting
// Switch with type patterns and guards
String format(Object obj) {
return switch (obj) {
case Integer i -> "int " + i;
case String s when s.length() > 5 -> "long string: " + s;
case String s -> "short string: " + s;
default -> "unknown";
};
}Full examples: patternmatching/UseShapes.java
- Null handling in switch expressions
- No more NullPointerException surprises
- Explicit null cases
- Cleaner than separate null checks
// Null can be handled directly in switch
String handleNulls(Object obj) {
return switch (obj) {
case null -> "was null";
case String s -> "string: " + s;
case Integer i -> "number: " + i;
default -> "something else";
};
}Full examples: patternmatching/UseShapes.java
- Record patterns for decomposition
- Extract values directly in switch
- Cleaner than accessor methods
- Direct access to record components
// Record patterns - destructuring
String processPoint(Object obj) {
return switch (obj) {
case Point(int x, int y) -> "point at (" + x + "," + y + ")";
default -> "not a point";
};
}
// Simpler than using accessor methods
String oldWay(Object obj) {
return switch (obj) {
case Point p -> "point at (" + p.x() + "," + p.y() + ")";
default -> "not a point";
};
}Full examples: patternmatching/UseShapes.java
- Works with nested records
- Destructure complex data structures
- Extract multiple levels at once
- Powerful for data processing
// Nested record patterns
String processShape(Object obj) {
return switch (obj) {
case Rectangle(Point(int x, int y), int width, int height) ->
"Rectangle at (" + x + "," + y + ") size " + width + "x" + height;
default -> "unknown shape";
};
}
// Partial destructuring - only what you need
String getX(Object obj) {
return switch (obj) {
case Rectangle(Point(int x, var _), var _, var _) -> "x = " + x;
default -> "no x coordinate";
};
}Full examples: patternmatching/UseShapes.java
- Great for domain modeling
- Enables exhaustive pattern matching
- Better than visitor pattern
- Limited use cases but powerful when needed
// Define allowed subclasses explicitly
public sealed interface Shape
permits Circle, Rectangle, Triangle {}
// Alternative syntax - classes in same file
public sealed class Animal
permits Dog, Cat, Bird {}Full examples: sealed/Shape.java, sealed/Circle.java
- Each permitted class must be final, sealed, or non-sealed
- Compiler enforces the permitted list
- All subclasses must be in same module
- Clean hierarchy control
// Each permitted class must choose its modifier
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double radius() { return radius; }
}Full examples: sealed/Circle.java, sealed/Rectangle.java
final- cannot be extended furthersealed- can specify its own permitted subclassesnon-sealed- reopens for extension- Choose based on your design needs
// Final class - end of hierarchy
public final class Triangle implements Shape {
private final double base, height;
// constructor, getters...
}
// Sealed class - extends the hierarchy
public sealed class Polygon implements Shape
permits Triangle, Pentagon, Hexagon {}
// Non-sealed class - reopens for extension
public non-sealed class FlexibleShape implements Shape {}Full examples: sealed/ directory
- Compiler knows all possible subtypes
- Enables exhaustive switch expressions
- No default case needed
- Compile-time safety for domain models
// Exhaustive pattern matching - no default needed!
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
};
}
// Compiler error if you miss a case
String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle with radius " + c.radius();
// Compile error: Rectangle and Triangle not covered!
};
}Full examples: sealed/ directory
- Game changer for concurrent apps
- Lightweight threads (use 1000x less memory)
- Simple thread-per-request model returns
- Makes reactive programming less necessary
// Virtual threads - millions of threads!
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(1000);
System.out.println("Task " + i);
});
});
} // Handles 10,000 concurrent tasks easily!
// Simple virtual thread
Thread.ofVirtual().start(() -> {
System.out.println("Hello from virtual thread!");
});Full examples: virtualthreads/VirtualThreadsDemo.java
- Controversial and often skipped
- Good: Strong encapsulation, explicit dependencies
- Bad: Complex migration, tooling issues
- Most projects still use classpath
// module-info.java - defines a module
module com.myapp {
requires java.base; // implicit
requires java.sql;
requires spring.boot;
exports com.myapp.api;
exports com.myapp.model;
opens com.myapp.model to jackson.databind;
}- Compilation requires module-path instead of classpath
- Running modules uses different syntax
- Tools and IDEs have varying support
- Migration from classpath can be challenging
# Compilation with modules
javac -d out \
--module-path libs \
module-info.java \
src/**/*.java
# Running a modular application
java --module-path out:libs \
--module com.myapp/com.myapp.Main
# Mixed mode (modules + classpath)
java --module-path mods \
--class-path libs/*.jar \
--module com.myapp/com.myapp.Main- Fills a real gap in collections
- Unified API for ordered collections
- No more list.get(list.size()-1)
- Natural addition to the API
// New interfaces with first/last access
SequencedCollection<String> list = new ArrayList<>();
list.add("first");
list.add("last");
// Direct access to first and last
String first = list.getFirst(); // "first"
String last = list.getLast(); // "last"
// Reversed view
SequencedCollection<String> reversed = list.reversed();
// Works with LinkedHashSet, LinkedHashMap too- Nice quality of life feature
- Makes intent clearer
- Reduces unused variable warnings
- Common in other languages
// Ignore values you don't need with _
try {
int result = someMethod();
} catch (Exception _) { // Don't care about exception
System.out.println("Failed");
}
// In loops - just counting
for (int _ : collection) {
count++;
}
// Pattern matching - only care about x
switch (obj) {
case Point(int x, _) -> System.out.println("x = " + x);
}- Handy for development
- Great for prototyping and testing
- Replaces python -m http.server
- Not for production use
# Start a web server in current directory
$ jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0".
Serving /current/directory and subdirectories on 127.0.0.1 port 8000
# Custom port and directory
$ jwebserver -p 3000 -d /path/to/files
# Or programmaticallyvar server = SimpleFileServer.createFileServer(
new InetSocketAddress(8080),
Path.of("/www"),
OutputLevel.VERBOSE
);
server.start();- Powerful but niche
- Replaces JNI (finally!)
- Better performance than JNI
- Complex API, steep learning curve
// Call C functions from Java - no JNI needed!
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
// Link to C's strlen function
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);- Safe memory management with Arena
- Automatic cleanup of native memory
- Type-safe function descriptors
- Better than unsafe operations
// Using the linked function safely
try (Arena arena = Arena.ofConfined()) {
// Allocate native memory for string
MemorySegment str = arena.allocateUtf8String("Hello!");
// Call the C function
long len = (long) strlen.invoke(str);
System.out.println("Length: " + len); // Output: 6
} // Memory automatically freed
// Another example - calling system time
MethodHandle time = linker.downcallHandle(
stdlib.find("time").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);
long currentTime = (long) time.invoke(MemorySegment.NULL);- Solves real concurrency pain points
- Better error handling
- Automatic cancellation
- Works great with virtual threads
// Structured concurrency - threads as a unit
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // Wait for all
scope.throwIfFailed(); // Propagate errors
return new Response(user.resultNow(), order.resultNow());
} // Automatically cancels any running tasks- Extends Streams API with custom intermediate operations
- Enables stateful transformations
- Process elements in groups or windows
- More flexible than traditional collectors
// Fixed window gatherer - non-overlapping groups
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6)
.gather(Gatherers.windowFixed(3))
.toList();
// Result: [[1, 2, 3], [4, 5, 6]]
// Sliding window gatherer - overlapping groups
List<List<Integer>> sliding = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.toList(); Full examples: streams/StreamGatherersTest.java, exercises/solutions/StreamGatherersTest.java
- Stateful reduction operations
- Fold reduces to single value
- Scan emits intermediate results
- Similar to reduce but more flexible
// Fold gatherer - reduces to single value
String concatenated = Stream.of("a", "b", "c")
.gather(Gatherers.fold(() -> "", String::concat))
.findFirst()
.orElse(""); // "abc"
// Scan gatherer - emits running totals
List<Integer> runningSum = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
// Result: [1, 3, 6, 10, 15]Full examples: streams/StreamGatherersTest.java, exercises/solutions/StreamGatherersTest.java
- Create your own intermediate operations
- Stateful transformations with custom logic
- Implement complex stream processing patterns
- Reusable across different stream pipelines
// Custom gatherer - distinct by property
Gatherer<Person, ?, Person> distinctByName =
Gatherer.of(
HashSet::new, // Initial state supplier
(state, element, downstream) -> {
if (state.add(element.name())) {
return downstream.push(element);
}
return true; // Continue processing
}
);Full examples: streams/StreamGatherersTest.java, exercises/solutions/StreamGatherersTest.java
- Control when to stop processing
- Implement take-while patterns with state
- Build conditional stream termination
- More flexible than
takeWhile
// Take elements until sum exceeds limit
Gatherer<Integer, ?, Integer> takeUntilSum(int limit) {
return Gatherer.ofSequential(
() -> new int[]{0}, // sum accumulator
(sum, num, downstream) -> {
sum[0] += num;
return sum[0] <= limit ? downstream.push(num) : false;
}
);
}Full examples: streams/StreamGatherersTest.java, exercises/solutions/StreamGatherersTest.java
- Group elements by time or other criteria
- Maintain complex state during processing
- Build powerful batch processing
- Useful for event stream processing
// Batch elements by time window
Gatherer<Event, ?, List<Event>> timeWindow(Duration window) {
return Gatherer.ofSequential(
() -> new ArrayList<>(),
(batch, event, downstream) -> {
if (!batch.isEmpty() &&
Duration.between(batch.get(0).time(), event.time())
.compareTo(window) > 0) {
downstream.push(new ArrayList<>(batch));
batch.clear();
}
batch.add(event);
return true;
}
);
}Full examples: streams/StreamGatherersTest.java, exercises/solutions/StreamGatherersTest.java
- Withdrawn for redesign
- Shows Java's commitment to getting it right
- May return in future (Java 26+?)
// Was in preview JDK 21-22, removed in 23
// The syntax was:
String name = "World";
String message = STR."Hello, \{name}!"; // "Hello, World!"
// With expressions
int x = 10, y = 20;
String calc = STR."\{x} + \{y} = \{x + y}"; // "10 + 20 = 30"
// SQL template processor (the dream)
PreparedStatement ps = SQL."SELECT * FROM users WHERE id = \{userId}";














