
Java 12 Features You Should Know
Java 12, released in March 2019, brought several significant improvements to the platform that developers and system administrators should understand when managing Java applications in production environments. While not an LTS release, Java 12 introduced important features that influenced subsequent versions, including enhanced garbage collection, preview language features, and improved JVM performance. This guide covers the most impactful Java 12 features with practical examples, performance considerations, and real-world applications for server deployments and application development.
Switch Expressions (Preview Feature)
Switch expressions were introduced as a preview feature in Java 12, addressing the verbose and error-prone nature of traditional switch statements. This feature allows switches to return values directly and introduces the arrow syntax for cleaner code structure.
// Traditional switch statement
String dayType;
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
dayType = "Weekday";
break;
case SATURDAY:
case SUNDAY:
dayType = "Weekend";
break;
default:
dayType = "Unknown";
}
// Java 12 switch expression
String dayType = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
case SATURDAY, SUNDAY -> "Weekend";
default -> "Unknown";
};
To enable preview features in Java 12, compile and run with specific flags:
javac --enable-preview --release 12 SwitchExample.java
java --enable-preview SwitchExample
Real-world applications include configuration mapping, HTTP status code handling, and state machine implementations where multiple conditions map to single outcomes.
Shenandoah Garbage Collector
Shenandoah GC became available as an experimental feature in Java 12, designed for low-latency applications requiring predictable pause times regardless of heap size. Unlike other collectors, Shenandoah performs concurrent collection work including concurrent compaction.
Enable Shenandoah with JVM flags:
# Enable Shenandoah GC
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC MyApplication
# With additional tuning
java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC \
-XX:ShenandoahGCHeuristics=adaptive \
-XX:+ShenandoahUncommit MyApplication
Garbage Collector | Pause Time | Throughput | Memory Overhead | Best Use Case |
---|---|---|---|---|
G1GC | ~10ms target | High | Medium | Balanced applications |
Shenandoah | <10ms consistent | Medium-High | Higher | Low-latency requirements |
ZGC | <10ms guaranteed | Medium | Highest | Very large heaps |
Shenandoah works particularly well for applications with heap sizes between 4GB-64GB where consistent response times matter more than absolute throughput.
Microbenchmark Suite
Java 12 included a built-in microbenchmark suite using JMH (Java Microbenchmark Harness), providing standardized performance testing capabilities for JVM development and application optimization.
Access the microbenchmarks in the OpenJDK source:
# Clone OpenJDK repository
git clone https://github.com/openjdk/jdk.git
cd jdk/test/micro
# Build and run specific benchmarks
make test TEST="micro:java.lang.StringBench"
# Run with specific parameters
java -jar benchmark.jar -i 10 -wi 5 -f 2 StringBench.concat
This suite enables developers to benchmark string operations, collection performance, and other core Java functionality against different JVM versions, helping identify performance regressions in production deployments.
JVM Constants API
The java.lang.constant package introduced a standard API for modeling nominal descriptions of class file and runtime artifacts. This supports dynamic language implementations and frameworks that generate bytecode at runtime.
import java.lang.constant.*;
// Create constant descriptors
ClassDesc stringClass = ClassDesc.of("java.lang.String");
MethodTypeDesc concatMethod = MethodTypeDesc.of(stringClass, stringClass, stringClass);
// Use in dynamic method generation
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = concatMethod.resolveConstantDesc(lookup);
Framework developers use this API for efficient bytecode generation, particularly in ORM frameworks, serialization libraries, and proxy generation tools where runtime class creation is common.
Compact Number Formatting
NumberFormat received compact number formatting support, enabling locale-aware abbreviated number representations commonly used in user interfaces and reporting systems.
import java.text.NumberFormat;
import java.util.Locale;
// Compact formatting examples
NumberFormat shortFormat = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
System.out.println(shortFormat.format(1500)); // "1.5K"
System.out.println(longFormat.format(1500)); // "1.5 thousand"
System.out.println(shortFormat.format(1500000)); // "1.5M"
// Different locales
NumberFormat germanShort = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.SHORT);
System.out.println(germanShort.format(1500)); // "1,5 Tsd."
This feature proves valuable for dashboard applications, financial reporting systems, and mobile applications where screen space constraints require abbreviated number displays while maintaining readability.
String API Enhancements
Java 12 added several utility methods to the String class, improving text processing capabilities without requiring external libraries.
// New String methods in Java 12
String text = " Hello World ";
String multiline = "Line 1\nLine 2\nLine 3\n";
// indent() - adds specified spaces to each line
String indented = multiline.indent(4);
System.out.println(indented);
/*
Line 1
Line 2
Line 3
*/
// describeConstable() - returns Optional descriptor
Optional descriptor = text.describeConstable();
// resolveConstantDesc() - resolves constant description
String resolved = text.resolveConstantDesc(MethodHandles.lookup());
These methods particularly benefit template processing, code generation, and configuration file manipulation where consistent indentation and text formatting are required.
Files.mismatch() Method
The Files class gained a mismatch() method for efficiently comparing large files by finding the first byte position where files differ, useful for file synchronization and backup verification.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
// Compare two files
Path file1 = Paths.get("/path/to/file1.txt");
Path file2 = Paths.get("/path/to/file2.txt");
long mismatchPosition = Files.mismatch(file1, file2);
if (mismatchPosition == -1L) {
System.out.println("Files are identical");
} else {
System.out.println("Files differ at byte position: " + mismatchPosition);
}
// Practical backup verification example
public boolean verifyBackup(Path original, Path backup) {
try {
return Files.mismatch(original, backup) == -1L;
} catch (IOException e) {
return false;
}
}
System administrators use this for backup integrity checks, file synchronization verification, and detecting corruption in replicated data without loading entire files into memory.
Collectors.teeing() Method
Stream processing received the teeing() collector, enabling parallel processing of stream elements through two different collectors, then combining results with a merger function.
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Calculate average and count simultaneously
List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
String result = numbers.stream()
.collect(Collectors.teeing(
Collectors.averagingDouble(Integer::doubleValue),
Collectors.counting(),
(avg, count) -> String.format("Average: %.2f, Count: %d", avg, count)
));
System.out.println(result); // "Average: 5.50, Count: 10"
// Real-world example: statistics collection
public class OrderStatistics {
public String analyzeOrders(Stream orders) {
return orders.collect(Collectors.teeing(
Collectors.summingDouble(Order::getAmount),
Collectors.groupingBy(Order::getStatus, Collectors.counting()),
(totalAmount, statusCounts) ->
String.format("Total: $%.2f, Status breakdown: %s",
totalAmount, statusCounts)
));
}
}
This collector proves efficient for analytics pipelines, report generation, and data processing scenarios where multiple aggregations on the same dataset are required.
JFR Event Streaming
Java Flight Recorder received streaming capabilities, allowing real-time monitoring and analysis of JFR events without waiting for recording completion. This enables continuous monitoring and immediate response to performance issues.
import jdk.jfr.consumer.RecordingStream;
// Stream JFR events in real-time
public class JFRMonitoring {
public void startMonitoring() {
try (RecordingStream rs = new RecordingStream()) {
// Enable specific events
rs.enable("jdk.GCHeapSummary");
rs.enable("jdk.CPULoad");
rs.enable("jdk.JavaMonitorEnter");
// Set up event handlers
rs.onEvent("jdk.GCHeapSummary", event -> {
long heapUsed = event.getLong("heapUsed");
long heapSize = event.getLong("heapSize");
double utilization = (double) heapUsed / heapSize * 100;
if (utilization > 90) {
System.out.println("WARNING: High heap utilization: " + utilization + "%");
}
});
rs.onEvent("jdk.CPULoad", event -> {
double machineTotal = event.getDouble("machineTotal");
if (machineTotal > 0.8) {
System.out.println("WARNING: High CPU load: " + (machineTotal * 100) + "%");
}
});
// Start streaming
rs.start();
}
}
}
Production monitoring systems benefit from this capability for implementing custom alerting, performance dashboards, and automated scaling decisions based on real-time JVM metrics.
Platform-Specific Desktop Features
Java 12 enhanced desktop integration with platform-specific improvements, including better support for high-DPI displays and native desktop integration features.
// Check for desktop support
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
// Enhanced file operations
if (desktop.isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
desktop.browseFileDirectory(new File("/path/to/file"));
}
// Improved mail integration
if (desktop.isSupported(Desktop.Action.MAIL)) {
URI mailUri = new URI("mailto:user@example.com?subject=Report&body=Content");
desktop.mail(mailUri);
}
}
// High-DPI awareness configuration
System.setProperty("sun.java2d.uiScale", "1.5");
System.setProperty("awt.useSystemAAFontSettings", "on");
These improvements particularly benefit desktop applications, development tools, and enterprise software requiring seamless OS integration.
Performance Improvements and Optimizations
Java 12 included numerous performance optimizations across the platform, affecting startup time, memory usage, and runtime performance.
- Improved class data sharing (CDS) reducing startup time by up to 30% for typical applications
- Enhanced escape analysis improving object allocation patterns
- Optimized string concatenation using invokedynamic
- Better NUMA awareness in garbage collectors
- Improved JIT compilation for lambda expressions
Benchmark comparisons show measurable improvements:
Metric | Java 11 | Java 12 | Improvement |
---|---|---|---|
Startup Time (typical app) | 2.1s | 1.5s | 28% faster |
String concat performance | baseline | +15% | 15% improvement |
Lambda cold start | baseline | +22% | 22% improvement |
These optimizations particularly benefit microservices, serverless functions, and applications with frequent restarts where startup time and initial performance matter significantly.
Best Practices and Common Pitfalls
When adopting Java 12 features in production environments, consider these implementation guidelines:
- Preview features like switch expressions require explicit enablement and should be evaluated carefully for production use
- Shenandoah GC needs thorough testing with your specific workload before production deployment
- JFR streaming can impact performance if too many events are enabled simultaneously
- Compact number formatting should be cached for frequently used formatters to avoid repeated locale processing
- The Constants API primarily benefits framework developers rather than application developers
Common troubleshooting scenarios include preview feature compilation errors, GC tuning for Shenandoah, and JFR event configuration issues. Always test new features in staging environments that mirror production workloads.
For comprehensive documentation and migration guides, refer to the official OpenJDK Java 12 documentation and the Java 12 Migration Guide.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.