How to Install and Set Up Java 25 on Your System

Java 25 is the latest version of the Java Development Kit (JDK), packed with performance improvements and new features. In this guide, you’ll learn how to install Java 25 on your system and write your first “Hello, World!” program using the new classless main method feature.

Tip: Java 25 introduces the ability to write simple programs without needing a class declaration. Perfect for beginners!


Prerequisites

Before we begin, make sure you have:

  • A computer with Windows, macOS, or Linux
  • A terminal or command prompt
  • Internet connection to download the JDK

Step 1: Download Java 25

  1. Go to the official JDK page: https://jdk.java.net/25

  2. Under Java SE Development Kit 25, choose the right version for your OS:

    • Windows: jdk-25_windows-x64_bin.zip or .msi
    • macOS: jdk-25_macos-x64_bin.tar.gz or .dmg
    • Linux: jdk-25_linux-x64_bin.tar.gz
  3. Download the installer or archive file.


Step 2: Install Java 25

Windows

  • If you downloaded the .msi file:

    • Double-click it and follow the installation wizard.
  • If you downloaded the .zip file:
    • Extract it to C:\Program Files\Java\jdk-25

Set up the environment variables:

setx JAVA_HOME "C:\Program Files\Java\jdk-25"
setx PATH "%JAVA_HOME%\bin;%PATH%"

macOS

Using tar.gz:

sudo mkdir -p /Library/Java/JavaVirtualMachines
sudo tar -xzf jdk-25_macos-x64_bin.tar.gz -C /Library/Java/JavaVirtualMachines/

Set environment variables (edit ~/.zshrc or ~/.bash_profile):

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-25.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH

Linux

Using tar.gz:

tar -xvzf jdk-25_linux-x64_bin.tar.gz
sudo mv jdk-25 /usr/lib/jvm/jdk-25

Update your environment (~/.bashrc or ~/.zshrc):

export JAVA_HOME=/usr/lib/jvm/jdk-25
export PATH=$JAVA_HOME/bin:$PATH

Then run:

source ~/.bashrc  # or source ~/.zshrc

Step 3: Verify the Installation

Open a terminal and type:

java -version

You should see something like:

java version "25" 2025-09-17
Java(TM) SE Runtime Environment (build 25+36)
Java HotSpot(TM) 64-Bit Server VM (build 25+36, mixed mode)

Step 4: Write Your First Java 25 Program

Java 25 allows you to write simple programs without declaring a class! Let’s try it.

  1. Create a file named Hello.java:
    void main() {
        System.out.println("Hello, Java 25!");
    }
    

    This is called classless main method syntax – available since JDK 21, but very useful in Java 25 for quick scripts!

  2. Compile and run it using:

java Hello.java

You’ll see:

Hello, Java 25!

You’re all set!


What’s Next?

Now that you’ve installed Java 25, try exploring:

  • Java 25 features like pattern matching, unnamed classes, class-file API
  • Writing simple scripts using .java files directly
  • Exploring new APIs introduced in Java 25

Stay tuned for more tutorials on using Java 25 effectively!


Summary

Step Action
1 Download Java 25 from jdk.java.net
2 Install based on your OS
3 Set JAVA_HOME and update PATH
4 Run java -version to verify
5 Create and run your first program

How do I parallelize a stream for performance?

To parallelize a stream in Java and improve performance, you can use the parallelStream method or convert a normal stream into a parallel stream using the Stream.parallel() method. Parallel streams allow data to be processed on multiple threads, leveraging multicore processors.

Here’s a detailed explanation and examples:

1. Using parallelStream()

You can use the parallelStream() method on a Collection (like a List, Set, etc.), which returns a parallel stream by default.

Example:

package org.kodejava.util.stream;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Process the stream in parallel
        numbers.parallelStream()
                .map(number -> number * 2) // Multiply each number by 2
                .forEach(System.out::println); // Print each element
    }
}

2. Using the parallel() Method

If you already have a sequential stream, you can convert it into a parallel stream using the Stream.parallel() method.

Example:

package org.kodejava.util.stream;

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        // Sequential stream
        IntStream.range(1, 11)
                .parallel() // Convert to parallel stream
                .map(i -> i * i) // Square each number
                .forEach(System.out::println); // Print squared numbers
    }
}

3. Custom Thread Pool for ForkJoinPool

By default, parallel streams use the common ForkJoinPool for task execution with a default number of threads. If you want to control the thread pool size (e.g., prevent overloading the system), you can supply a custom ForkJoinPool.

Example:

package org.kodejava.util.stream;

import java.util.List;
import java.util.concurrent.ForkJoinPool;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        ForkJoinPool customThreadPool = new ForkJoinPool(4); // Limit to 4 threads

        customThreadPool.submit(() ->
            numbers.parallelStream()
                    .map(number -> number * 2)
                    .forEach(System.out::println)
        ).join();

        customThreadPool.shutdown();
    }
}

Key Points About Parallel Streams

  1. Performance Consideration:
    • Parallel streams divide their workload into smaller chunks and process them concurrently. Thus, they’re best suited for CPU-intensive operations or for working with large datasets.
    • For smaller datasets, the overhead of parallelism might actually degrade performance compared to a sequential stream.
  2. Thread-Safety:
    • Ensure your pipeline operations are thread-safe. For instance, avoid shared mutable state in stream operations as it can lead to race conditions.
  3. Order and Results:
    • Parallel streams might not maintain the processing order unless explicitly required. If you want to maintain order, consider using operations like forEachOrdered() instead of forEach().

    Example with forEachOrdered():

    numbers.parallelStream()
           .map(number -> number * 2)
           .forEachOrdered(System.out::println); // Maintain order
    
  4. Parallelization is Not Always Optimal:
    • Parallel streams are more effective when the processing of individual elements is computationally expensive or when the dataset is large.
    • For small datasets or lightweight operations, the cost of managing threads can outweigh the performance benefits.

Summary

  • Use parallelStream() or Stream.parallel() to parallelize your stream.
  • Optimize the operations in the stream pipeline to take full advantage of parallel processing.
  • Be cautious with thread-safety and order requirements.
  • Profile and test your application to confirm that parallel streams provide a tangible performance boost in your specific use case.

How do I use Stream.peek() for debugging?

The Stream.peek method in Java’s Stream API is an invaluable utility for debugging your stream pipeline. It provides a way to inspect (or “peek at”) the elements of your stream during the processing without modifying them. This is typically used for logging or debugging purposes.

Here’s how Stream.peek works and how you can use it for debugging:

How Stream.peek Works

  • The peek method takes a Consumer as an argument. A Consumer is a functional interface that takes an input and performs some operation without returning any result.
  • peek operates on each element of the stream as it passes through, allowing you to perform side effects, such as logging the current state of each element.
  • It is particularly useful for observing intermediate data in a stream processing pipeline.

Syntax

Stream<T> peek(Consumer<? super T> action)
  • Parameters: action – a non-interfering action (side effect) that will be invoked on each stream element as it gets processed.
  • Returns: Returns a new stream identical to the original but with the action applied to each element as a side effect.

Note: Since streams in Java are lazy (operations don’t execute until a terminal operation is invoked), the peek method will only execute when a terminal operation (like collect, forEach, reduce, etc.) is triggered.

Example of Using peek for Debugging

1. Logging Intermediate Elements

package org.kodejava.util.stream;

import java.util.stream.Stream;

public class PeekExample {
    public static void main(String[] args) {
        Stream.of("one", "two", "three", "four") // Create the stream
                .filter(str -> str.length() > 3)    // Filter elements with length > 3
                .peek(str -> System.out.println("After filter: " + str)) // Debug filtered elements
                .map(String::toUpperCase)          // Map to uppercase
                .peek(str -> System.out.println("After map: " + str)) // Debug mapped elements
                .forEach(System.out::println);     // Final terminal operation
    }
}

Output:

After filter: three
After filter: four
After map: THREE
THREE
After map: FOUR
FOUR

In this example:

  • peek is used after the filter and map stages to print the elements at each point in the stream pipeline.
  • This allows you to understand how elements are being processed step-by-step.

2. Debugging a Processing Sequence

Suppose you have some complex logic in your stream pipeline, and you want to verify the intermediate results during processing:

package org.kodejava.util.stream;

import java.util.Arrays;
import java.util.List;

public class StreamPeekDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        List<Integer> result = numbers.stream()
                .filter(num -> num % 2 == 0)                  // Keep only even numbers
                .peek(num -> System.out.println("Filtered: " + num)) // Debug filtered numbers
                .map(num -> num * num)                       // Square the numbers
                .peek(num -> System.out.println("Mapped: " + num))   // Debug mapped (squared) numbers
                .toList();                                   // Terminal operation (collect to list)

        System.out.println("Final result: " + result);
    }
}

Output:

Filtered: 2
Mapped: 4
Filtered: 4
Mapped: 16
Filtered: 6
Mapped: 36
Final result: [4, 16, 36]

3. Warnings While Using peek

  • Don’t use peek to modify state: Ideally, peek should only be used for debugging and observing, not for state modification. If you need to modify elements, prefer using map.
  • Streams are lazy: The peek method doesn’t execute until a terminal operation (e.g., forEach, collect) is invoked. Make sure your terminal operation is actually being called.
  • Avoid side effects: While peek supports side effects like logging or inspection, avoid introducing side effects that interfere with the expected behavior of your application.

Key Points

  • Use Stream.peek for debugging to inspect the state of elements at specific stages in a stream pipeline.
  • It does not modify the stream elements, making it ideal for logging or tracing intermediate results.
  • Streams only execute when a terminal operation like forEach, collect, or reduce is called.
  • Avoid using peek for critical logic; it’s best for debugging or observational purposes only.

By adding peek strategically in your stream pipeline, you can trace how your data is transformed step by step!

How do I convert a list to map with collectors toMap safely?

In Java, you can safely convert a List to a Map using Collectors.toMap by ensuring that duplicate keys or null values are handled appropriately. Here’s how you can achieve this:

Safe Conversion Approach:

When working with Collectors.toMap, it’s important to keep the following in mind:

  1. Handle Key Collisions: If multiple elements map to the same key, a java.lang.IllegalStateException will be thrown. To avoid this, provide a merge function that decides what happens in the case of duplicate keys.
  2. Null Values: Avoid null keys or values unless your use case explicitly allows them.

Example Code:

package org.kodejava.util.stream;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ListToMapExample {
    public static void main(String[] args) {
        List<String> fruits = List.of("apple", "banana", "cherry", "apple");

        // Safely convert List to Map with a merge function to handle key collisions
        Map<String, Integer> fruitMap = fruits.stream()
                .collect(Collectors.toMap(
                        fruit -> fruit,          // Key mapper: the fruit itself
                        fruit -> fruit.length(), // Value mapper: the length of the fruit name
                        (existing, replacement) -> existing // Merge function: keep the existing value
                ));

        System.out.println(fruitMap);
    }
}

Explanation:

  1. Key Mapper: fruit -> fruit maps each list item to itself as a key.
  2. Value Mapper: fruit -> fruit.length() calculates the length of each item as the value.
  3. Merge Function: (existing, replacement) -> existing ensures the map keeps the original value for duplicate keys (e.g., for "apple", the first occurrence’s value will be kept).
  4. Result:
    Output for the example list would be:

    {apple=5, banana=6, cherry=6}
    

Immutable Map:

If you want the resulting Map to be immutable, you can use Collectors.toUnmodifiableMap (Java 10+):

Map<String, Integer> fruitMap = fruits.stream()
    .collect(Collectors.toUnmodifiableMap(
        fruit -> fruit,
        fruit -> fruit.length(),
        (existing, replacement) -> existing
    ));

Here:

  • Any attempts to modify the map (e.g., adding or replacing entries) will throw UnsupportedOperationException.

Notes:

  • For Java 8, you can create immutable maps using Collections.unmodifiableMap() after performing the collection:
Map<String, Integer> fruitMap = Collections.unmodifiableMap(
    fruits.stream()
        .collect(Collectors.toMap(
            fruit -> fruit,
            fruit -> fruit.length(),
            (existing, replacement) -> existing
        ))
);

This ensures safety during the conversion and follows best practices when handling potential issues.

How do I use Map.Entry comparingByValue for sorting?

To use Map.Entry.comparingByValue for sorting a Map, you can leverage Java Streams, which provide an efficient way to process and sort collection data. Here’s how the process works:

  1. Retrieve the entrySet of the Map: This gives a set of Map.Entry objects that you can operate on with a stream.
  2. Sort using Map.Entry.comparingByValue: Use Stream.sorted() along with this comparator to sort the entries by their values.
  3. Collect the sorted entries into a LinkedHashMap: Preserve the sorted order by using a LinkedHashMap in combination with Collectors.toMap.

Here’s a step-by-step explanation in a generic template:

Code Example

Below is an example of sorting a Map<String, Integer> by its values using Map.Entry.comparingByValue:

package org.kodejava.util.stream;

import java.util.*;
import java.util.stream.*;

public class MapSortExample {
    public static void main(String[] args) {
        // Sample map
        Map<String, Integer> map = new HashMap<>();
        map.put("Apple", 10);
        map.put("Orange", 20);
        map.put("Banana", 5);

        // Sorting the map by value
        Map<String, Integer> sortedByValue = map.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByValue()) // Default ascending order
                .collect(Collectors.toMap(
                        Map.Entry::getKey,        // Key mapper
                        Map.Entry::getValue,      // Value mapper
                        (oldValue, newValue) -> oldValue, // Merge function
                        LinkedHashMap::new        // Map type (preserves order)
                ));

        // Printing sorted map
        sortedByValue.forEach((key, value) ->
                System.out.println("Key: " + key + ", Value: " + value));
    }
}

Key Points

  1. Map.Entry.comparingByValue():
    • It returns a comparator that compares Map.Entry objects by their values in ascending order.
    • You can use .reversed() to reverse the order (for descending order).
  2. Preserve Order:
    • The LinkedHashMap is used when collecting to ensure the order of sorted entries is retained.
    • Other maps (e.g., HashMap) won’t maintain the sorted order.
  3. Custom Comparators:
    • If values in the map are objects other than Integer, you can provide a custom comparator to comparingByValue() for sorting purposes:
      Map.Entry.comparingByValue(Comparator.reverseOrder());
      
    • For ascending sorting, the default is enough.

  4. Streams:

    • The stream() method converts the entrySet of a map to a stream.
    • The sorted() operation applies the comparator to order the entries within the stream.
  5. Merging Duplicate Keys:
    • (oldValue, newValue) -> oldValue ensures no duplicate keys during the collection phase.

This approach is concise, leverages modern Java features, and ensures efficient sorting and processing.