Showing posts with label Java 18. Show all posts
Showing posts with label Java 18. Show all posts

Wednesday, October 11, 2023

Javadoc documents are great—and better than ever with @snippets

JEP 413, delivered in Java 18, makes Java documentation easier to create than ever.


Say goodbye to incorrect or outdated code snippets in Javadoc documentation! The new @snippet tag, delivered in JEP 413 with Java 18, is an effective solution for including code snippets in the documentation. This improvement will facilitate better code quality, improve software development, and encourage more developers to use the Java API.

Java developers write functional code, and they are also responsible for documenting it correctly, as Andrew Binstock explained in “Reduce technical debt by valuing comments as much as code.” One way of documenting your Java application is by using the Javadoc tool, which has long used tagged comments you insert within your source code to describe parameters, return values, exceptions thrown, and so on. In some cases, it is necessary to include a code snippet to demonstrate the intended use of the code.

Before Java 18, the @code tag and the <pre> and </pre> tags were used to indicate multiline snippets, and adding code examples to API documentation using Javadoc was a tedious process. Special characters such as <, >, and & had to be escaped, and handling indentation was difficult. The biggest issue, however, was that a code snippet had to be specified within the Javadoc comment itself. This made it challenging to create code snippets, and there needed to be a way to verify if the code was accurate. The result? Sometimes you had code snippets that wouldn’t compile, either due to an author oversight or API changes that weren’t reflected in the Javadoc comments. That really limited the value of code snippets as documentation.

However, there’s always room for improvement, and that’s precisely where JEP 413 comes in. The code snippets in the Java API documentation proposal specified in JEP 413 revolutionizes Java API documentation by making it more user friendly and accessible to developers. It’s now easier to include inline code snippets as well as external source files within the documentation. This will enable you to edit, refactor, compile, and test the example code using your regular Java toolchain.

Adding code snippets to Javadoc documentation will help future maintainers of your code understand better how to use your classes and methods. They’ll have an easier time writing quality code, and software development will improve overall.

Code snippets can come in the following three forms:

  • Inline: A snippet that contains the content of the snippet within the tag itself
  • External: A snippet that refers to a separate class or file that contains the content of the snippet
  • Hybrid: A snippet that contains both internal and external content

The @snippet tag


JEP 413 introduced a new tag, {@snippet ...}, for code fragments in Javadoc-generated documentation. A snippet may have attributes in the form of name=value pairs, and values can be quoted with a single-quote character (') or a double-quote (") character. These name=value pairs can be added to the tag to provide more details and may include an id to identify the snippet in both the API and in the generated HTML, which may be used to create a link to the snippet.

Snippet code fragments are typically Java source code, but they can be other types of content. They may have a lang attribute to identify the kind of content (such as properties files, source code, or plain text) in the snippet (the default is Java) or to infer the type of line comment or end-of-line comment that may be supported in that language.

You can add markup tags (such as @highlight, @link, and @replace) within the code to indicate how to present the text, usually in the form of @name arguments.

Inline snippets


The most straightforward usage is to use an inline @snippet tag and include some code (or text) between the first new line after the colon (:) and the closing curly brace (}) of the tag itself, as in the following example:

/**
 * A demo of the @snippet tag to include non-escaping special characters.

 * {@snippet id='specialChars' :
 * int age = 42;
 * <html>Here is HTML tags works</html>
 * & also works;
 * @ also works;
 * }
 */

public void snippetsSpecialCharacters() {
    System.out.println("Code @snippets special characters demo.");
}

Then, the Javadoc function of your IDE-generated documentation will be similar to what’s shown in Figure 1.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 1. The new Javadoc snippet functionality shows how special characters work without requiring an escape sequence.

You should immediately notice a few improvements over the <pre>{@code ...}</pre> way of creating Javadoc documentation.

  • It’s easier to type with less clutter in the source file.
  • There’s no need to escape special characters such as <, @, >, or & with HTML entities.
  • The documentation has a grey background, which makes snippets more visible.
  • In the upper right corner, there’s a Copy button that copies snippets to the clipboard in plain text.

By the way, when you’re using inline snippets, the content can’t have the character pair */ because that would terminate the Javadoc comment. Also, parsing @snippet source code means Unicode escape sequences (\uNNNN) will be interpreted and can’t be differentiated from characters. In addition, all curly bracket characters ({}) must be balanced (that is, have the same number of left and right curly brackets nested) so the closing curly bracket of the @snippet tag can be determined.

Regions. Regions define a range of lines in a snippet and the scope for actions such as text highlighting or modification. Regions are marked by @start region=name or by @highlight/@replace/@link tags. A region ends with @end or @end region=name, which ends the region that has the matching name. You can create overlapping regions, but I wouldn’t recommend it; it could be confusing.

Highlighting. Use the @highlight tag to highlight text within a line or a region and to specify the content to highlight as a literal string (using a substring argument) or as a regular expression (using a regex argument). Additionally, use region to define the scope for where to find the text to be highlighted, and use type to determine the type of highlighting by using bold, italic, or highlighted. If no type is specified, the entire line is formatted in bold. In the following example, a basic regular expression is used to highlight the content of a string literal:

/**
 *  Demo for code @snippets highlighting.
 * {@snippet id="highlighting" lang="java" :
 * (1) System.out.println("Code highlighting with 'regex'"); // @highlight regex='".*"'
 * (2) public record Point (int x, int y){}; // @highlight type=highlighted
 * (3) Point point = new Point(10, 30); // @highlight
 *
 * // @highlight region="output" substring="System.out" type="italic" :
 * (4 -->)
 * (5) System.out.println(point); // @highlight substring="point" type="italic"
 * (6) System.out.println(point.x()); // @highlight substring="point" :
 * (7) System.out.println(point.y()); // @highlight substring="out" type="highlighted"
 * (8) System.out.println(point.y()); // @highlight substring="y()" type="highlighted"
 * (<-- 4)
 * // @end region="output"
 * }
 */

public void demoHighlight() {
    System.out.println("Code @snippets highlighting demo.");
}

Running your IDE’s Javadoc function on the previous code snippet would generate the output shown in Figure 2.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 2. Code snippets that demonstrate highlighting

How does the output correspond to the resulting Javadoc documentation? In line 1 of the code example, //@highlight regex='".*"' marks all the content between double quotes in the println() method to be formatted in bold, the default highlighting type. In line 2 of the code example, I wanted to highlight the whole line with an orange background, so I used //@highlight type=highlighted. In line 3 of the code example, I wanted to highlight the entire code for the creation of the Point object point and its assignment to coordinates, so I used only //@highlight; the whole line is formatted in bold.

In line 5 of the code example, I used slightly more-complex highlighting by marking only one word (a substring)—point—in italics. In this case, I specified the substring and the highlighting using //@highlight substring="point" type="italic".

The first four examples highlighted something in the same line of code. However, you can also control the highlighting of the next line in the code snippet. For instance, in line 6 of the code example, I applied highlighting to the next line in the code snippet by adding a colon (:) at the end of line 6. The space before the colon is optional, but it enhances readability. Thus, point (in the point.y() part) will become bold, as shown in line 7 of Figure 2.

A potential problem can occur due to personal style: Some developers might prefer highlighting to affect the current line, but others might prefer highlighting to affect the next line. Both ways are permitted by the JEP 413 specification. My recommendation: Stick to one convention within your codebase.

To add background color for a specific part of the code, use type="highlighted"—as shown in line 7 of the code example. There, the //@highlight substring="out" type="highlighted" formats out with an orange background in line 7 of Figure 2.

You might have observed that out is also written in italics in line 7 of Figure 2. This is because in the code example, the code between (4 -->) and (<-- 4) uses the //@highlight substring=System.out type=italic region=output definition, which applies italic highlighting to all matching substrings in this region. Since all the substrings are italicized, the preceding line adds an orange background to out, and this demonstrates how highlights can be combined.

Finally, all named regions must be closed with the //@end markup tag: //@end region=output.

Linking. You can provide additional information in Javadoc documentation by linking a text fragment to other parts of the documentation, whether to internal parts (such as to other documentation comments on the same page) or to external content (such as to a documentation comment of another class or even to a JDK class). To accomplish this, use the @link markup tag within the contents of @snippet followed by arguments to link the text to declarations in the API.

The arguments should specify the scope of the text to be considered, the text within that scope to be linked, and the link’s target. If region or region=name is provided, the scope will be that region up to the corresponding @end tag. If neither is specified, the scope will be limited to the current line.

To link each instance of a literal string within the scope, use the substring=string attribute. To link each instance of text matched by a regular expression within the scope, use regex=string. If neither of these attributes is specified, the entire scope will be linked. You can set the target with the target parameter and specify the type of link: link (the default) or linkplain. Consider the following example:

/**
 *  Demo for code @snippets Linking.
 * {@snippet id="linking" lang="java" :
 * // @link substring="System.out" target="System#out":
 * System.out.println("Link to System.out");
 * final App app = new App();
 * // @link substring="demoLinking()" target="#demoLinking" type="link" :
 * app.demoLinking();
 * }
 */
    
public void demoLinking() {
    System.out.println("Code @snippets Linking demo.");
}

Figure 3 shows the corresponding Javadoc documentation.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 3. How to link code snippets

The opening line in the Javadoc documentation shown in Figure 3 links to the well-known out instance of the java.lang.System class. The icon displayed next to System.out is worth noting; it indicates this is an external link. In contrast, no icon appears next to demoLinking(), because it’s an internal link. Like many hyperlinks on the internet, the color of these links is adjusted when you hover over them, serving as another sign that they are clickable.

Modifying text. Sometimes you might want to perform a search-and-replace operation using a placeholder value in the text of an example and then use a marker comment to indicate that the placeholder should be substituted with alternate text in the final output.

Use @replace to replace some text with replacement text within a line or region and follow that with arguments that specify the scope of the text to be considered. Use substring with the literal expression or regex with a regular expression for the text to be replaced, and set the region to define the scope for where to find the text to be replaced. Finally, use replacement for the replacement text. Consider the following example:

/**
 *  Demo for code @snippets Text Replacement.
 * {@snippet id="linking" lang="java" :
 *   public static void main(String... args) {
 *      var text = "Mohamed Taman"; // @replace regex='\".*\"' replacement=" ... "
 *      System.out.println(text);   // @replace substring='System.' replacement=""
 *   }
 * }
 */

public void demoReplacement() {
    System.out.println("Code @snippets Text Replacement Demo.");
}

In the example above, a text variable of type substring with the value Mohamed Taman is used as the placeholder value, and the @replace tag is used to specify that it should be replaced with an ellipsis. (I doubt you’ll find a real-life application for that specific example.)

Here are two final tricks. To delete text, use @replace with an empty string as I did for System. To insert text, replace nonoperative text with @replace. No-op text can be // or an empty statement.

You can see the output for the code above in Figure 4.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 4. Code snippets after text replacement

External snippets

Although inline code snippets are suitable for many scenarios, there may be instances where more-advanced functionality is required. For example, external code snippets are necessary to integrate external code snippets with block comments that refer to a separate class or file containing the snippets’ content.

You can place external files in a subdirectory, called snippet-files, of the package that contains the snippet tag. Alternatively, when you run Javadoc, you can specify a separate directory using the --snippet-path option.

The example below demonstrates how to lay out the external snippet. It consists of a directory named src/org, which contains the source code for a class named App.Main, an image named icon.png in the doc-files subdirectory, and several files for external snippets—ExternalSnippet.java, ExternalSnippetWithRegions.java, external-prop-Snippet.properties, and external-html-snippet.html—in the snippet-files directory. The StandardDoclet class can locate the external snippets in this example without additional options.

$ tree
       .
└── src
           └── org
  ├── App.java
  ├── doc-files
  │ └── icon.png
  └── snippet-files
                    ├── ExternalSnippet.java
                    ├── ExternalSnippetWithRegions.java
                    ├── external-prop-Snippet.properties
                    └── external-html-snippet.html

The external file for a snippet can be specified by using either the class attribute for Java source files or the file attribute for other file types. Here’s an example of a snippet referencing an external class called ExternalSnippet.java that has the following contents.

public class ExternalSnippet {
    /**
     * The ubiquitous "Hello, World!" program.
     */
    public static void main(String... args) {
        System.out.println("Hello, World!");
    }
}

You can reference ExternalSnippet.java in the code snippet as follows:

/**
 *  Demo for code @snippets with external code snippets.
 * {@snippet class=ExternalSnippet }
 */

public void demoExternalSnippet() {
    System.out.println("Code @snippets referencing external java file Demo.");
}

As you can see, the colon, newline, and subsequent content can be omitted in an external snippet. However, it is not surprising that the generated output, shown in Figure 5, looks similar to the external source file.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 5. Code snippets can reference an external Java file.

Referencing non-Java files. External snippets aren’t restricted to Java source files. Any structured text suitable for display in an HTML <pre> element can be used. You can reference non-Java files such as external-html-snippet.html and the properties file external-prop-Snippet.properties. Consider the following external-html-snippet.html example:

<!DOCTYPE html>
<html lang="en">
    <body>
        <ol>
            <li>Hello Snippets</li>
            <li>I am an external HTML file to include in the @snippet</li>
        </ol>
    </body>
</html>

You can reference it with the following:

/**
  *  Demo for code @snippets with external html file.
  * {@snippet file='external-html-snippet.html' }
  */

public void demoExternalSnippetHtml() {
    System.out.println("Code @snippets referencing external html file Demo.");
}

The output will appear as shown in Figure 6.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides

Figure 6. Code snippets can reference an external HTML file.

Using only parts of an external file. Now imagine that you have the following external properties external-prop-Snippet.properties file, and you want to reference a specific region (region=house) and highlight the street name inside that region. The file’s content would be as follows:

client.name=Mohamed Taman
# @start region=house
house.number=42
# @highlight substring="Durmitorska St." :
house.street=Durmitorska St.
house.town=Belgrade, Serbia
# @end region=house
client.email=mohamed.taman@gmail.com

Here’s how to reference the file inside the @snippet tag.

/**
 *  Demo for code @snippets with external properties file with highlighting.
 * {@snippet file='external-prop-Snippet.properties' region=house }
 */

public void demoExternalSnippetProperties() {
    System.out.println(""" 
          Code @snippets referencing external
          properties file with highlighting Demo.
          """);
}

The output will be as shown in Figure 7.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides
Figure 7. A code snippet that references one region of an external properties file

Formatting and linking within an external Java snippet. Regrettably, there is a drawback when you use highlighting with external snippets. You cannot define a region using @highlight region=reg1 (or @link region=reg1 or @replace region=reg1) and then refer to that region within the main source file. The only option is to define a region in an external snippet using @start region=reg1. Therefore, you can highlight, replace, or link to only a single line within the region.

Note: This limitation doesn’t exist for internal snippets, as shown in the previous external properties file example, Figure 7, or the following Java-based example:

public class ExternalSnippetWithRegions {
    /**
     * The ubiquitous "Hello, Snippets World!" program.
     */

    public static void main(String... args) {
        // @start region=main
        /*
          Print Hello world
         */
        System.out.println("Inside region Main"); // @highlight regex='".*"'

        // Join a series of strings // @start region=join
        var delimiter = " ";        // @replace regex='".*"' replacement='"..."'
        var result = String.join(delimiter, args); // @link substring="String.join" target="String#join"
        // @end region=join
        // @end region=main
    }
}

When the above is referenced as follows

/**
 *  Demo for code @snippets with external Java code file with region and highlight.
 * {@snippet class=ExternalSnippetWithRegions region=main}
 */

public void demoExternalSnippetRegions() {
    System.out.println("Code @snippets referencing external java file region Demo.");
}

it will be rendered as shown in Figure 8.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides
Figure 8. Code snippets can reference an external Java file with region and highlight.

Hybrid snippets

External snippets are convenient for testing purposes. Inline snippets offer context within a comment. Hybrid snippets combine the benefits of both, though with slightly less convenience. They include attributes for specifying an external file and possibly a region.

To ensure consistency, StandardDoclet verifies that processing the snippet as both inline and external snippets yields the same result. It’s recommended that you develop the snippet initially as either an inline or external snippet and then convert it to a hybrid snippet later.

The example below merges an inline and an external snippet using region=join (from the previous ExternalSnippetWithRegions.java example) to create a hybrid snippet. It’s worth noting that the inline content differs slightly from the region’s content in the external snippet. The external snippet utilizes a @replace tag to ensure it is compilable code, while the inline snippet displays ... directly for readability purposes.

The content from the external file ExternalSnippetWithRegions.java is as follows:

public class ExternalSnippetWithRegions {
  ...
  // Join a series of strings // @start region=join
  var delimiter = " ";        // @replace regex='".*"' replacement='"..."'
  var result = String.join(delimiter, args); // @link substring="String.join"      
  target="String#join"
 // @end region=join
 ...
}

And the inline snippet is as follows:

/**
 * Demo for code Hybrid Snippets.
 * {@snippet class=ExternalSnippetWithRegions region=join :
 * // Join a series of strings
 * var delimiter = "...";
 * var result = String.join(delimiter, args);
 * }
 */

public void demoHybridSnippets() {
    System.out.println("Code for Hybrid Snippets Demo.");
}

The output is shown in Figure 9.

Oracle Java, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials, Java Guides
Figure 9. Hybrid code snippets

Source: oracle.com

Friday, May 19, 2023

What Java Version is Java 18? A Comprehensive Guide

Oracle Java Certification, Java Skills, Java Jobs, Java Learning, Java Preparation, Java Guides

As technology continues to advance at a rapid pace, it's crucial for developers and businesses to stay up to date with the latest programming languages and frameworks. Java, being one of the most popular and widely used programming languages, has seen numerous updates and versions over the years. In this comprehensive guide, we will delve into the details of Java 18 and shed light on its features, improvements, and the benefits it brings to the table.

Introduction to Java 18


Java 18 is the latest version in the Java programming language series, released by Oracle Corporation. This release builds upon the strengths of its predecessors, introducing new features, enhancements, and bug fixes to improve performance, security, and developer productivity. It provides developers with a more robust and efficient environment for creating enterprise-level applications, web services, and mobile applications.

Key Features of Java 18


1. Pattern Matching for Switch: One of the standout features of Java 18 is the enhanced pattern matching for switch statements. This allows developers to write more concise and expressive code by matching complex patterns directly in switch cases. It simplifies the codebase, improves readability, and reduces the chances of introducing bugs.

2. Records: Java 18 introduces the concept of records, which are immutable classes specifically designed to store data. Records provide a concise syntax for defining classes that only contain state and automatically generate useful methods such as constructors, accessors, and equals() and hashCode() implementations. They simplify the process of creating immutable data objects and contribute to writing clean and maintainable code.

3. Sealed Classes: With Java 18, sealed classes are introduced, allowing developers to control the inheritance hierarchy of their classes. By explicitly specifying which classes can extend or implement a sealed class, developers gain better control over their codebase, ensuring encapsulation and preventing unauthorized inheritance. This feature promotes design clarity and enhances code robustness.

4. Foreign Function & Memory API (Incubator): Java 18 introduces the Foreign Function & Memory API as an incubator module, enabling developers to efficiently and safely access native code and memory from Java. This opens up new possibilities for integration with existing native libraries, system-level programming, and low-level memory management.

5. Enhanced Packed Objects (Incubator): Another incubating feature in Java 18 is Enhanced Packed Objects (EPO). EPO provides a more memory-efficient representation for objects by eliminating padding bytes and optimizing memory usage. This can result in significant memory savings, particularly when working with large data sets or in resource-constrained environments.

Benefits of Upgrading to Java 18


Upgrading to Java 18 offers numerous benefits for developers, businesses, and end-users alike. Here are some of the key advantages:

1. Improved Performance: Java 18 brings performance optimizations, including faster startup times, reduced memory footprint, and enhanced garbage collection algorithms. These improvements lead to more responsive applications and better overall system performance.

2. Enhanced Security: Keeping up with the latest Java version ensures that your applications are protected against security vulnerabilities. Oracle regularly addresses security issues and provides patches and updates to mitigate potential risks. By upgrading to Java 18, you can leverage the latest security enhancements and protect your systems from emerging threats.

3. Language Enhancements: Java 18 introduces language enhancements, such as pattern matching and records, that enable developers to write cleaner, more concise, and expressive code. These features enhance developer productivity, simplify code maintenance, and reduce the likelihood of introducing bugs.

4. Access to New Libraries and Frameworks: With each new Java version, a multitude of libraries and frameworks are updated or released, offering developers access to cutting-edge tools and resources.

5. Compatibility and Support: Upgrading to Java 18 ensures compatibility with the latest ecosystem of tools, libraries, and frameworks. It allows you to leverage the advancements and updates made by the Java community, ensuring that your applications can integrate seamlessly with other components of your technology stack.

6. Bug Fixes and Performance Enhancements: Java 18 incorporates bug fixes and performance enhancements from previous versions, addressing known issues and providing a more stable and efficient environment for your applications. These improvements contribute to smoother execution, reduced downtime, and enhanced user experience.

7. Long-Term Support (LTS): Java 18 is an LTS release, which means it receives long-term support from Oracle. LTS releases are recommended for production environments as they provide extended support, including bug fixes and security patches, for an extended period. This ensures stability, reliability, and peace of mind for businesses relying on Java for their critical systems.

Migrating to Java 18


Migrating your existing Java applications to Java 18 requires careful planning and consideration. Here are some steps to help you navigate the migration process:

1. Evaluate Compatibility: Assess the compatibility of your current codebase and dependencies with Java 18. Identify any potential issues or conflicts that may arise during the migration process. Ensure that your libraries, frameworks, and third-party components are compatible with Java 18 or have updated versions available.

2. Test and Refactor: Create a comprehensive test suite to validate the behavior and performance of your application after the migration. Refactor your codebase to leverage new language features, remove deprecated APIs, and optimize performance where possible. Conduct thorough testing to ensure the stability and correctness of your application.

3. Update Build and Deployment Tools: Update your build and deployment tools to support Java 18. This includes updating your build scripts, configuring your continuous integration/continuous deployment (CI/CD) pipelines, and ensuring compatibility with your chosen development environment or IDE.

4. Gradual Rollout: Consider a gradual rollout strategy, starting with non-production environments and gradually migrating to production. This approach allows you to identify and resolve any issues in a controlled manner, minimizing the impact on your users and ensuring a smooth transition.

5. Monitoring and Performance Tuning: Monitor the performance of your application after the migration. Identify any performance bottlenecks or areas for optimization. Fine-tune your application to leverage the performance improvements introduced in Java 18, ensuring optimal resource utilization and responsiveness.

Conclusion

Java 18 brings a host of new features, enhancements, and performance improvements that can significantly benefit developers and businesses. By upgrading to Java 18, you can take advantage of the latest language capabilities, improve application performance, enhance security, and ensure compatibility with the evolving Java ecosystem.

Migrating to Java 18 requires careful planning, testing, and refactoring. However, the benefits of upgrading far outweigh the challenges, as it provides a solid foundation for developing robust, secure, and high-performing applications.

Stay ahead of the curve by embracing Java 18 and unlocking its potential for your software development projects.

Wednesday, June 8, 2022

Bruce Eckel on Java pattern matching guards and dominance

Core Java, Java Career, Java Jobs, Java Skills, Java Preparation, Oracle Java Certification, Java Guides, Oracle Java Tutorial and Materials

Pattern matching guards let you refine the matching condition beyond simply matching on the type.

The previous article in this series, “Bruce Eckel on pattern matching in Java,” introduced pattern matching and this article delves into the details. Keep in mind that, at the time of this writing, pattern matching for switch is a second preview feature in JDK 18 as JEP 420, and the Java team intends to add additional features.

Guards

A guard allows you to refine the matching condition beyond simply matching on the type. It is a test that appears after the type and &&. The guard can be any Boolean expression. If the selector expression is the same as the type for the case and the guard evaluates to true, the pattern matches, as follows:

// enumerations/Shapes.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Shape {

  double area();

}

record Circle(double radius) implements Shape {

  @Override public double area() {

    return Math.PI * radius * radius;

  }

}

record Rectangle(double side1, double side2)

  implements Shape {

  @Override public double area() {

    return side1 * side2;

  }

}

public class Shapes {

  static void classify(Shape s) {

    System.out.println(switch(s) {

      case Circle c && c.area() < 100.0

        -> "Small Circle: " + c;

      case Circle c -> "Large Circle: " + c;

      case Rectangle r && r.side1() == r.side2()

        -> "Square: " + r;

      case Rectangle r -> "Rectangle: " + r;

    });

  }

  public static void main(String[] args) {

    List.of(

      new Circle(5.0),

      new Circle(25.0),

      new Rectangle(12.0, 12.0),

      new Rectangle(12.0, 15.0)

    ).forEach(t -> classify(t));

  }

}

/* Output:

Small Circle: Circle[radius=5.0]

Large Circle: Circle[radius=25.0]

Square: Rectangle[side1=12.0, side2=12.0]

Rectangle: Rectangle[side1=12.0, side2=15.0]

*/

(The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

The first guard for Circle determines whether that Circle is small. The first guard for Rectangle determines whether that Rectangle is a square.

Here’s a more complex example: A Tank can hold different types of liquids, and the Level of the tank must be between zero and 100%.

// enumerations/Tanks.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

enum Type { TOXIC, FLAMMABLE, NEUTRAL }

record Level(int percent) {

  Level {

    if(percent < 0 || percent > 100)

      throw new IndexOutOfBoundsException(

        percent + " percent");

  }

}

record Tank(Type type, Level level) {}

public class Tanks {

  static String check(Tank tank) {

    return switch(tank) {

      case Tank t && t.type() == Type.TOXIC

        -> "Toxic: " + t;

      case Tank t && (                 // [1]

          t.type() == Type.TOXIC &&

          t.level().percent() < 50

        ) -> "Toxic, low: " + t;

      case Tank t && t.type() == Type.FLAMMABLE

        -> "Flammable: " + t;

      // Equivalent to "default":

      case Tank t -> "Other Tank: " + t;

    };

  }

  public static void main(String[] args) {

    List.of(

      new Tank(Type.TOXIC, new Level(49)),

      new Tank(Type.FLAMMABLE, new Level(52)),

      new Tank(Type.NEUTRAL, new Level(75))

    ).forEach(

      t -> System.out.println(check(t))

    );

  }

}

The record Level includes a compact constructor that ensures that percent is valid. Records were introduced in a previous article, “Bruce Eckel on Java records,” in this series.

Here’s a note for line [1]: If a guard contains multiple expressions, simply enclose it in parentheses.

Since the code switches on Tank rather than Object, the final case Tank acts the same as a default because it catches all Tank cases that don’t match any of the other patterns.

Dominance

The order of the case statements in a switch can be important because if the base type appears first, it dominates anything appearing afterwards.

// enumerations/Dominance.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

import java.util.*;

sealed interface Base {}

record Derived() implements Base {}

public class Dominance {

  static String test(Base base) {

    return switch(base) {

      case Derived d -> "Derived";

      case Base b -> "B";            // [1]

    };

  }

}

The base type Base is in last place, at line [1]—and that’s where it should be. But if you move that line up, the base type will appear before case Derived, which would mean that the switch would never be able to test for Derived because any derived class would then be captured by case Base. If you try this experiment, the compiler reports an error: this case label is dominated by a preceding case label.

Order sensitivity often appears when you use guards. Moving the final case in Tanks.java to a higher position in the switch produces that same domination error message. When you have multiple guards on the same pattern, more-specific patterns must appear before more-general patterns. Otherwise, a more-general pattern will match before more-specific patterns, and the latter will never be checked. Fortunately, the compiler reports dominance problems.

The compiler can detect dominance problems only when the type in a pattern dominates the type in another pattern. The compiler cannot know whether the logic in guards produces problems.

// enumerations/People.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

record Person(String name, int age) {}

public class People {

  static String categorize(Person person) {

    return switch(person) {

      case Person p && p.age() > 40          // [1]

        -> p + " is middle aged";

      case Person p &&

        (p.name().contains("D") || p.age() == 14)

        -> p + " D or 14";

      case Person p && !(p.age() >= 100)     // [2]

        -> p + " is not a centenarian";

      case Person p -> p + " Everyone else";

    };

  }

  public static void main(String[] args) {

    List.of(

      new Person("Dorothy", 15),

      new Person("John Bigboote", 42),

      new Person("Morty", 14),

      new Person("Morty Jr.", 1),

      new Person("Jose", 39),

      new Person("Kane", 118)

    ).forEach(

      p -> System.out.println(categorize(p))

    );

  }

}

/* Output:

Person[name=Dorothy, age=15] D or 14

Person[name=John Bigboote, age=42] is middle aged

Person[name=Morty, age=14] D or 14

Person[name=Morty Jr., age=1] is not a centenarian

Person[name=Jose, age=39] is not a centenarian

Person[name=Kane, age=118] is middle aged

*/

The guard in pattern line [2] seems like it would match Kane at age 118, but instead Kane matches with the pattern at line [1]. You cannot rely on the compiler to help with the logic of your guard expressions.

Without the last case Person p, the compiler complains that the switch expression does not cover all possible input values. With that case, a default is still not required, so the most general case becomes the default. Because the argument to the switch is a Person, all cases are covered (except for null).

Coverage

Pattern matching naturally guides you toward using the sealed keyword. This helps ensure that you’ve covered all possible types passed into the selector expression. See how that works in practice with the following example:

// enumerations/SealedPatternMatch.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Transport {};

record Bicycle(String id) implements Transport {};

record Glider(int size) implements Transport {};

record Surfboard(double weight) implements Transport {};

// If you uncomment this:

// record Skis(int length) implements Transport {};

// You get an error: "the switch expression

// does not cover all possible input values"

public class SealedPatternMatch {

  static String exhaustive(Transport t) {

    return switch(t) {

      case Bicycle b -> "Bicycle " + b.id();

      case Glider g -> "Glider " + g.size();

      case Surfboard s -> "Surfboard " + s.weight();

    };

  }

  public static void main(String[] args) {

    List.of(

      new Bicycle("Bob"),

      new Glider(65),

      new Surfboard(6.4)

    ).forEach(

      t -> System.out.println(exhaustive(t))

    );

    try {

      exhaustive(null); // Always possible!  // [1]

    } catch(NullPointerException e) {

      System.out.println("Not exhaustive: " + e);

    }

  }

}

/* Output:

Bicycle Bob

Glider 65

Surfboard 6.4

Not exhaustive: java.lang.NullPointerException

*/

The sealed interface Transport is implemented using record objects, which are automatically final. The switch covers all possible types of Transport, and if you add a new type, the compiler detects it and tells you that you haven’t exhaustively covered all possible patterns. But line [1] shows that there’s still one case that the compiler doesn’t insist you cover: null.

If you remember to explicitly add a case null, you’ll prevent the exception. But the compiler doesn’t help you here, possibly because that would affect too much existing switch code.

Source: oracle.com

Friday, May 27, 2022

The not-so-hidden gems in Java 18: The JDK Enhancement Proposals

Five of Java 18’s JEPs add new features or major enhancements. See what they do and how they work.

Java 18 was officially released in March, and it included nine implemented JDK Enhancement Proposals (JEPs).

This article covers five of the JEPs in Java 18. The companion article, “The hidden gems in Java 18: The subtle changes,” goes over Java 18’s small improvements and changes that aren’t reflected in those JEPs. It also talks about the four JEPs that are incubators, previews, and deprecations.

Because these articles were researched prior to the March 22 general availability date, I used the Java 18 RC-build 36 jshell tool to demonstrate the code. However, if you would like to test the features, you can follow along with me by downloading the latest release candidate (or the general availability version), firing up a terminal window, checking your version, and running jshell, as follows. Note that you might see a newer version of the build.

[mtaman]:~ java -version

 openjdk version "18" 2022-03-22

 OpenJDK Runtime Environment (build 18+36-2087)

 OpenJDK 64-Bit Server VM (build 18+36-2087, mixed mode, sharing)

[mtaman]:~ jshell --enable-preview

|  Welcome to JShell -- Version 18

|  For an introduction type: /help intro

jshell>

The nine JEPs in the Java 18 release as are follows:

◉ JEP 400: UTF-8 by default

◉ JEP 408: Simple Web Server

◉ JEP 413: Code snippets in Java API documentation

◉ JEP 416: Reimplement core reflection with method handles

◉ JEP 417: Vector API (third incubator)

◉ JEP 418: Internet address resolution service provider interface

◉ JEP 419: Foreign Function and Memory API (second incubator)

◉ JEP 420: Pattern matching for switch (second preview)

◉ JEP 421: Deprecate finalization for removal

Two of the JEPs are incubator features, one is a preview, and is one is a deprecation, that is, a JEP that prepares developers for the removal of a feature. In this article, I’ll walk through the other five JEPs, and I will not explore the incubators, the preview, and the deprecation.

JEP 400: UTF-8 by default

UTF-8 is a variable-width character encoding for electronic communication and is considered the web’s standard character set (charset). It can encode all the characters of any human language.

The Java standard charset determines how a string is converted to bytes and vice versa. It is used by many methods of the JDK library classes, mainly when you’re reading and writing text files using, for example, the FileReader, FileWriter, InputStreamReader, OutputStreamWriter, Formatter, and Scanner constructors, as well as the URLEncoder encode() and decode() static methods.

Prior to Java 18, the standard Java charset could vary based on the runtime environment’s language settings and operating systems. Such differences could lead to unpredictable behavior when an application was developed and tested in one environment and then run in another environment where the Java default charset changed.

Consider a pre-Java 18 example that writes and reads Arabic text. Run the following code on Linux or macOS to write Arabic content (which means “Welcome all with Java JEP 400”) to the Jep400-example.txt file:

private static void writeToFile() {

   try (FileWriter writer = new FileWriter("Jep400-example.txt");

   BufferedWriter bufferedWriter = new BufferedWriter(writer)) {

      bufferedWriter.write("مرحبا بكم في جافا جيب ٤٠٠");

      }

   catch (IOException e) {

   System.err.println(e);

   }

}

Then read the file back using a Windows system and the following code:

private static void readFromFile() {

   try (FileReader reader = new FileReader("Jep400-example.txt");

   BufferedReader bufferedReader = new BufferedReader(reader)) {

      String line = bufferedReader.readLine();

      System.out.println(line);

      }

   catch (IOException e) {

   System.err.println(e);

   }

}

The output will be the following:

٠رحبا ب٠٠٠٠جا٠ا ج٠ب Ù¤Ù Ù

This rubbish output happens because, under Linux and macOS, Java saves the file with the default UTF-8 encoding format, but Windows reads the file using the Windows-1252 encoding format.

This problem becomes even worse when you mix text in the same program by writing the file using an older facility, FileWriter, and then reading the contents using newer I/O class methods such as Files writeString(), readString(), newBufferedWriter(), and newBufferedReader(), as in the following:

private static void writeReadFromFile() {

   try (FileWriter writer = new FileWriter("Jep400-example.txt");

   BufferedWriter bufferedWriter = new BufferedWriter(writer)) {

      bufferedWriter.write("مرحبا بكم في جافا جيب ٤٠٠");

      bufferedWriter.flush();

      var message = Files.readString(Path.of("Jep400-example.txt"));

      System.out.println(message);

      }

   catch (IOException e) {

   System.err.println(e);

   }

}

Running the program under Linux and macOS will print the Arabic content without any problem, but doing so on Windows will print the following:

????? ??? ?? ???? ??? ???

These question marks are printed because newer APIs don’t respect the default OS character set and always used UTF-8; so in this case, the file is written with FileWriter using the Windows-1252 charset. When Files.readString(Path.of("Jep400-example.txt")) reads back the file, the method use UTF-8 regardless of the standard charset.

The best solution is to specify the charset when you’re reading or writing files and when calling all methods that convert strings to bytes (and vice versa).

You might think another solution is to set the default charset with the file.encoding system property, as follows:

var fileWriter = new FileWriter("Jep400-example.txt", StandardCharsets.UTF_8);

var fileReader = new FileReader("Jep400-example.txt", StandardCharsets.UTF_8);

Files.readString(Path.of("Jep400-example.txt"), StandardCharsets.UTF_8);

What happens?

[mtaman]:~ java -Dfile.encoding=US-ASCII FileReaderWriterApplication.java

?????????????????????????????????

The result is rubbish because the FileWriter respects the default encoding system property, while Files.readString() ignores it and uses UTF-8. Therefore, the correct encoding should be used to run correctly with -Dfile.encoding=UTF-8.

The goal of JEP 400 is to standardize the Java API default charset to UTF-8, so that APIs that depend on the default charset will behave consistently across all implementations, operating systems, locales, and configurations. Therefore, if you run the previous snippets on Java 18, the code will produce the correct contents.

JEP 408: Simple Web Server

Many development environments let programmers start up a rudimentary HTTP web server to test some functionality for static files. That capability comes to Java 18 through JEP 408.

The simplest way to start the web server is with the jwebserver command. By default, this command listens to localhost on port 8000. The server also provides a file browser to the current directory.

[mtaman]:~ jwebserver 

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".

Serving /Users/mohamed_taman/Hidden Gems in Java 18/code and subdirectories on 127.0.0.1 port 8000

URL http://127.0.0.1:8000/

If you visit http://127.0.0.1:8000/, you will see a directory listing in your web browser, and in the terminal you will see the following two lines:

127.0.0.1 - - [06/Mar/2022:23:27:13 +0100] "GET / HTTP/1.1" 200 -

127.0.0.1 - - [06/Mar/2022:23:27:13 +0100] "GET /favicon.ico HTTP/1.1" 404 –

You can change the jwebserver defaults with several parameters, as follows:

[mtaman]:~ jwebserver -b 127.1.2.200 -p 9999 -d /tmp -o verbose 

Serving /tmp and subdirectories on 127.0.0.200 port 9999

URL http://127.0.0.200:9999/

Here are a few of the parameters.

◉ -b specifies the IP address on which the server should listen.

◉ -p changes the port.

◉ -d changes the directory the server should serve.

◉ -o configures the log output.

For a complete list of configuration options, run jwebserver -h.

The web server is simple, as its name implies, and has the following limitations:

◉ Only the HTTP GET and HEAD methods are allowed.

◉ The only supported protocol is HTTP/1.1.

◉ HTTPS is not provided.

The API. Fortunately, you can extend and run the server programmatically from the Java API. That’s because jwebserver itself is not a standalone tool; it is a wrapper that calls java -m jdk.httpserver. This command calls the main() method of the sun.net.httpserver.simpleserver.Main class of the jdk.httpserver module, which, in turn, calls SimpleFileServerImpl.start(). This starter evaluates the command-line parameters and creates the server via SimpleFileServer.createFileServer().

You can start a server via Java code as follows:

// Creating the server

HttpServer server = SimpleFileServer.createFileServer(new InetSocketAddress(9999), Path.of("\tmp"), OutputLevel.INFO);

// Starting the server

server.start();

With the Java API, you can extend the web server. For example, you can make specific file system directories accessible via different HTTP paths, and you can implement your handlers to use your choice of paths and HTTP methods, such as PUT and POST. Check the JEP 408 documentation for more API details.

JEP 413: Code snippets in Java API documentation

Prior to Java 18, if you wanted to integrate multiline code snippets into Java documentation (that is, into Javadoc), you had to do it tediously via <pre> ... </pre> and sometimes with {@code ...}. With these tags, you must pay attention to the following two points:

◉ You can’t put line breaks between <pre> and </pre>, which means you may not get the formatting you want.

◉ The code starts directly after the asterisks, so if there are spaces between the asterisks and the code, they also appear in the Javadoc.

Here’s an example that uses <pre> and </pre>.

/**

  * How to read a text file with Java 8:

  *

  * <pre><b>try</b> (var reader = Files.<i>newBufferedReader</i>(path)) {

  *    String line = reader.readLine();

  *    System.out.println(line);

  * }</pre>

  */

This code will appear in the Javadoc as shown in Figure 1.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 1. Example using <pre> and </pre>

Here’s an example that uses <pre> and {@code ...}.

/**
     * How to read a text file with Java 8:
     *
     * <pre>{@code try (var reader = Files.newBufferedReader(path)) {
     *    String line = reader.readLine();
     *    System.out.println(line);
     * }}</pre>
     */

Figure 2 shows the output Javadoc.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 2. Example using <pre> and {@code}

The difference between the two examples is that in the first example with <pre>, you can format the code with HTML tags such as <b> and <i>, but in the second example with {@code}, such tags would not be evaluated but instead the code would be displayed as is.

JEP 413 introduces the @snippet tag for Javadoc’s standard doclet to simplify the inclusion of example source code in the API documentation. Among the goals of JEP 413 are the following:

◉ Facilitating the validation of source code fragments by providing API access to those fragments; however, correctness is the author’s responsibility

◉ Enabling modern styling, such as syntax highlighting and the automatic linkage of names to declarations

◉ Promoting better IDE support for creating and editing snippets

Using the @snippet tag, you can rewrite the example from Figure 1, which used the <pre> tag, as follows:

/**
     * How to read a text file with Java 8:
     * {@snippet :
     *  try (var reader = Files.newBufferedReader(path)) {
     *      String line = reader.readLine();
     *      System.out.println(line);
     *  }
     * }
     */

Figure 3 shows how this rewritten code would appear in the Javadoc.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 3. Example using @snippet

You can do more with @snippet. For instance, you can highlight parts of the code using @highlight. The following code highlights the readLine() method within the second line of code:

/**
     * How to read a text file with Java 8:
     * {@snippet :
     *  try (var reader = Files.newBufferedReader(path)) {
     *      String line = reader.readLine(); // @highlight substring="readLine()"
     *      System.out.println(line);
     *  }
     * }
     */

The output Javadoc appears as shown in Figure 4.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 4. Example using @highlight

Within the block marked with @highlight and @end below, you can highlight all the words beginning with line by using type="…", which can specify one of the following: bold, italic, or highlighted (with a colored background):

/**
     * How to read a text file with Java 8:
     * {@snippet :
     * // @highlight region regex="\bline\b" type="highlighted"
     * try (var reader = Files.newBufferedReader(path)) {
     *     String line = reader.readLine();
     *     System.out.println(line);
     *  }
     * // @end
     * }
     */

Then the code would appear in the Javadoc as shown in Figure 5.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 5. Example using type=

Using @link, you can link a part of the text, such as Files.newBufferedReader, to its Javadoc. Note that the colon at the end of the line with the @link tag is essential in this case, and it means that the comment refers to the next line.

/**
     * How to read a text file with Java 8:
     * {@snippet :
     * // @link substring="Files.newBufferedReader" target="Files#newBufferedReader" :
     * try (var reader = Files.newBufferedReader(path)) {
     *      String line = reader.readLine();
     *      System.out.println(line);
     * }
     * }
     */

Figure 6 shows how the code would appear in the Javadoc.

JDK Enhancement Proposals, Oracle Java Exam Prep, Oracle Java Certification, Java Career, Java Jobs, Java Skills, Java News, Java Process, Oracle Java Preparation Exam

Figure 6. Example using @link

You could also write the comment at the end of the following line, just like in the first @highlight example, or you could use @link and @end to specify a part within which all occurrences of Files.newBufferedReader should be linked.

JEP 416: Reimplement core reflection with method handles


Sometimes I want to do Java reflection, such as reading a private id field of a Person object’s class via reflection.

package org.java.mag.j18.reflection;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Person {
    private Long id;
    private String name;
}

Surprisingly, there are two ways to do that. First, I can use core reflection, as follows:

private static Long getLongId(Object obj) throws Exception {
          Field id = obj.getClass().getDeclaredField("id");
          id.setAccessible(true);
          return (Long) id.get(obj);
      }

Alternatively, I can use method handles, as shown below.

private static Long getLongId2(Object obj) throws Exception {
          VarHandle handle =
                 MethodHandles.privateLookupIn(Person.class, MethodHandles.lookup())
                        .findVarHandle(Person.class, "id", Long.class);
          return (Long) handle.get(obj);
      }

If you call both options from main, you’ll see that they print the same value, verifying that they both work.

public static void main(String[] args) throws Exception {
   Person person = Person.builder()
                  .id(2L)
                  .name("Mohamed Taman")
                  .build();

   System.out.println("Person Id (Core reflection): " + getLongId(person));
   System.out.println("Person Id (method handles): " + getLongId2(person));
      }

Person Id (Core reflection): 2
Person Id (method handles):  2

A third hidden option happens under the hood: leveraging additional native JVM methods used by the core reflection for the first few calls after starting the JVM. After a while, the JVM begins compiling and optimizing the Java reflection bytecode.

Maintaining all three of these options would necessitate a significant amount of effort from the JDK team. As a result, the core reflection reimplementation with method handles, as in JEP 416, now reimplements lang.reflect.Method, Constructor, and Field on top of java.lang.invoke method handles. The use of method handles as the underlying mechanism for reflection reduces the maintenance and development costs of the java.lang.reflect and java.lang.invoke APIs; thus, it is good for the future of the platform.

JEP 418: Internet address resolution service provider interface


JEP 418 enhances the currently limited implementation of java.net.InetAddress by developing a service provider interface (SPI) for hostname and address resolution. The SPI allows java.net.InetAddress to use resolvers other than the operating system’s native resolver, which is usually set up to use a combination of a local hosts file and the domain name system (DNS).

Here are the benefits of providing Java with name and address resolution SPI resolvers.

◉ It enables the seamless integration of new emerging network protocols such as DNS over Quick UDP Internet Connections (QUIC), Transport Layer Security (TLS), or HTTPS.

◉ Customization gives frameworks and applications more control over resolution results and the ability to retrofit existing libraries with a custom resolver.

◉ It would allow Project Loom to work more efficiently. The current InetAddress API resolution operation blocks an OS call, which is a problem for Loom’s user-mode virtual threads. Platform threads cannot service other virtual threads while waiting for a resolution operation to be completed. Therefore, an alternative resolver could directly implement the DNS client protocol without blocking.

◉ It would allow developers more control of hostname and address resolution results when prototyping and testing, which is often required.

Here’s a quick example that doesn’t use the new JEP 418 SPI to obtain all the IP addresses of a hostname (blogs.oracle.com). You can use one of the InetAddress class methods, such as getByName(String host) or getAllByName(String host). For reverse lookups (to get the IP address of the hostname), use getCanonicalHostName() or getHostName(), as follows:

public static void main(String[] args) throws UnknownHostException {
    InetAddress[] addresses = InetAddress.getAllByName("blogs.oracle.com");
    System.out.println("address(es) = " + Arrays.toString(addresses));
}

By default, InetAddress uses the operating system’s resolver, and on my computer this code prints the following:

Address(es) = [ blogs.oracle.com/2.17.7.152, 
                       blogs.oracle.com/2a02:26f0:b5:18a:0:0:0:a15, 
                       blogs.oracle.com/2a02:26f0:b5:18d:0:0:0:a15]

Next, try that sample task using an SPI in Java 18 to change the resolution address for internal use. First, create a resolver by implementing the interface java.net.spi.InetAddressResolver.

public class MyInetAddressResolver implements InetAddressResolver {
    @Override
    public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
        return Stream.of(InetAddress.getByAddress(new byte[]{127, 0, 0, 4}));
    }

    @Override
    public String lookupByAddress(byte[] addr) {
        return null;
    }
}

This code implements preliminary functionality, but it doesn’t support reverse lookups. Therefore implement a resolver provider by implementing java.net.spi.InetAddressResolverProvider, which returns an instance of a previously implemented resolver by its get() method, as follows:

import java.net.spi.InetAddressResolver;
import java.net.spi.InetAddressResolverProvider;

public class MyInetAddressResolverProvider extends InetAddressResolverProvider {
    @Override
    public InetAddressResolver get(Configuration configuration) {
        return new MyInetAddressResolver();
    }

    @Override
    public String name() {
        return "Java Magazine Internet Address Resolver Provider";
    }
}

Finally, register this resolver SPI to be used by the JDK by creating a file with the name java.net.spi.InetAddressResolverProvider under the META-INF/services folder, adding to the file the following entry: org.java.mag.j18.intaddr.MyInetAddressResolverProvider.

Run the same code in the main method as before, and you should see the following output:

addresses = [/127.0.0.4]

Source: oracle.com