Java has quietly grown up over the last few years. If your experience with the language stopped around Java 8, you're missing out on features that make code cleaner, shorter, and easier to read. Modern Java syntax you should be using, explained simply covers the practical improvements that ship in recent releases—features like var, text blocks, and switch expressions that transform how we write everyday code.
Why Modern Java Syntax Matters
Teams shipping production code in Java benefit when everyone writes in a consistent, readable style. The language has introduced features specifically designed to reduce boilerplate and eliminate verbose patterns that became tedious over decades of use. You're not adopting these features to be trendy; you're adopting them because they make pull requests shorter and bugs harder to hide.
We'll walk through the features you should be reaching for in new code today, with real scenarios from payments, configuration, and data handling that our teams encounter regularly.
Using var for Local Variables with Obvious Types
The var keyword, introduced in Java 10, lets the compiler infer the type of a local variable from its initializer. Instead of writing the full type name, you write var and let the compiler figure out what you're holding.
Here's the difference:
// Before: repetitive and verbose
HashMap<String, Order> orderCache = new HashMap<String, Order>();
List<PaymentMethod> methods = repository.findAll();
// After: clearer intent
var orderCache = new HashMap<String, Order>();
var methods = repository.findAll();Notice the right-hand side is where the type information lives. When you write new HashMap<String, Order>(), the compiler knows exactly what var should be. This isn't dynamic typing—the variable is still strongly typed at compile time.
var shines when you're working with collections, streams, or method results where the type is already clear from context. In an e-commerce scenario, when you fetch a list of orders from a repository, everyone reading the code knows var orders holds a collection of orders because the method name and return type are explicit.
When to use var: Local variables in methods where the type is obvious from the assignment. Prefer var for iterators in loops, results from well-named methods, and builder patterns.
When not to use var: Class fields (use explicit types), method parameters (type information is part of the contract), and cases where the initializer doesn't make the type obvious at a glance.
// Good: the type is clear
var user = userRepository.findById(userId);
var transactions = account.getRecentTransactions();
// Bad: what is this?
var x = process(data, config, true);Text Blocks for SQL, JSON and HTML Literals
Before Java 13, writing SQL queries, JSON strings, or HTML templates inside Java meant escaping quotes and manually concatenating lines. Text blocks changed that.
A text block is a multi-line string literal enclosed in triple quotes, and the compiler handles indentation intelligently.
// Before: painful to read and maintain
String query = "SELECT id, email, status FROM orders WHERE " +
"customer_id = ? AND created_at > ? " +
"ORDER BY created_at DESC";
// After: reads naturally
var query = """
SELECT id, email, status FROM orders
WHERE customer_id = ? AND created_at > ?
ORDER BY created_at DESC
""";Text blocks preserve newlines and indentation within the string, making them perfect for embedded SQL in production applications. No more counting quotes or squinting at concatenated lines.
In a SaaS product handling customer data, you might embed JSON configuration:
var config = """
{
"payment_provider": "stripe",
"retry_policy": {
"max_attempts": 3,
"backoff_ms": 1000
},
"webhook_url": "https://api.example.com/webhooks"
}
""";
// Use it directly with JSON parser
var settings = objectMapper.readValue(config, PaymentConfig.class);For travel and booking systems that work with HTML emails, text blocks eliminate quote escaping nightmares:
var emailBody = """
<h1>Your booking confirmation</h1>
<p>Reservation ID: %s</p>
<p>Check-in: %s</p>
<p>Total: $%.2f</p>
""".formatted(bookingId, checkinDate, total);Notice the formatted() method at the end—a companion feature that works smoothly with text blocks for parameter substitution.
Switch Expressions Return Values and Need No Break
Traditional Java switches are statements: they perform side effects and don't return values. You reach for them grudgingly and often end up with a variable assignment and repeated code.
Switch expressions, available since Java 12 (and finalized in Java 14), are different. They return a value directly, and the arrow syntax means you don't need break statements.
Here's a real payment processing scenario:
// Before: classic switch statement
String label;
switch (status) {
case PAID:
label = "Paid";
break;
case REFUNDED:
label = "Refunded";
break;
case PENDING:
label = "Pending";
break;
default:
label = "Unknown";
}
// After: switch expression
var label = switch (status) {
case PAID -> "Paid";
case REFUNDED -> "Refunded";
case PENDING -> "Pending";
default -> "Unknown";
};The switch expression returns the value directly. No intermediate variable, no break statements, no fall-through bugs. The compiler enforces that every branch returns a value.
Switch expressions handle complex logic too. If a branch needs multiple statements, use braces and an explicit yield:
var fee = switch (accountType) {
case PREMIUM -> {
logger.info("Premium account, applying tier discount");
yield calculatePremiumFee(baseAmount);
}
case STANDARD -> calculateStandardFee(baseAmount);
case TRIAL -> 0;
default -> throw new IllegalArgumentException("Unknown account type");
};In trading systems, you often map order states to UI labels or processing rules. Switch expressions keep that logic concise and verifiable—the compiler ensures you handle every case.
Pattern Matching for instanceof Removes Casts
The instanceof operator has existed forever, but it forced you into a two-step dance: check the type, then cast. Pattern matching, introduced in Java 16 as a preview and finalized in Java 21, fuses those steps.
// Before: check, then cast
if (payment instanceof CardPayment) {
CardPayment cardPayment = (CardPayment) payment;
processCard(cardPayment.getCardToken());
}
// After: pattern matching
if (payment instanceof CardPayment cardPayment) {
processCard(cardPayment.getCardToken());
};The variable cardPayment is available only within the if block, scoped exactly where it's needed. No redundant cast, no chance of accidentally using the wrong variable.
Pattern matching is especially useful in polymorphic scenarios. In a ride-hailing or CRM system, you might have different notification types:
public void handleNotification(Event event) {
if (event instanceof UserSignupEvent userEvent) {
sendWelcomeEmail(userEvent.getEmail());
} else if (event instanceof PaymentConfirmedEvent paymentEvent) {
recordTransaction(paymentEvent.getAmount());
} else if (event instanceof RideCompletedEvent rideEvent) {
updateDriverRating(rideEvent.getDriverId(), rideEvent.getRating());
}
}Each branch binds the variable with the correct type automatically. No casting, no mistakes, and the code reads as intent: "if this event is a signup, handle it this way."
Common Mistakes
- Using var with unclear initializers:
var result = process(x, y)whereprocesshas an obscure return type. The compiler knows the type, but teammates reading the code may not. Stick with explicit types when the initializer isn't self-documenting. - Oversizing text blocks: Text blocks are great for SQL and JSON, but don't use them to replace string concatenation in complex formatting logic. They're for literals, not computed strings.
- Forgetting default in switch expressions: Unlike switch statements, switch expressions require a
defaultbranch (unless your cases cover all enum values). The compiler enforces this because every path must return a value. - Pattern matching on null:
instanceofreturns false for null, soif (x instanceof SomeType var)safely handles null without throwing. But don't rely on this implicitly; make null handling explicit if it matters.
Key Takeaways
- Use
varfor local variables where the type is obvious from the assignment, reducing visual noise in methods. - Text blocks eliminate escaping and concatenation for multi-line strings like SQL, JSON, and HTML—especially valuable in production systems.
- Switch expressions return values directly with arrow syntax, replacing the error-prone switch statement pattern.
- Pattern matching fuses type checking and casting into a single, scoped operation, making polymorphic code cleaner and safer.
- These features shipped in Java 10+; use them confidently in Spring Boot and modern frameworks—they're stable and production-ready.






Comments
Loading comments…