Skip to content

Typos and small issues in "File System Basics" articles of "The Java I/O API" tutorial ( https://dev.java/learn/java-io/file-system/ ) #210

Closed

Description

@willy-b

Hello dev.java team!

Thanks so much for all this excellent content.

Just opening an issue to report some typos and small issues I found in the "File System Basics" articles of the "The Java I/O API" tutorial ( https://dev.java/learn/java-io/file-system/ ).


For the "Listing the Content of a Directory" article, https://dev.java/learn/java-io/file-system/listing/ (archived as is at https://web.archive.org/web/20260307205120/https://dev.java/learn/java-io/file-system/listing/ )

Image
  • code snippet is missing the space after "new" and before "DirectoryStream"
DirectoryStream.Filter<Path> filter =
    newDirectoryStream.Filter<Path>() {

(not to be confused with the "newDirectoryStream(Path, DirectoryStream.Filter) method", this is creating an instance of an anonymous class implementing the interface https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/DirectoryStream.Filter.html )

  • and, in the same snippet, the variable "file" is mis-referenced as path and Files.isDirectory() does NOT throw IOException, so that try-catch should be removed:
DirectoryStream.Filter<Path> filter =
    newDirectoryStream.Filter<Path>() { // should be `new DirectoryStream.Filter<Path>()` (missing space)
    public boolean accept(Path file) throws IOException {
        try {
            return (Files.isDirectory(path)); // SHOULD BE `(file)` NOT `(path)`
        } catch (IOException x) { // <-- remove this, Files.isDirectory does not throw exceptions (see https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/Files.html#isDirectory(java.nio.file.Path,java.nio.file.LinkOption...) )
            // Failed to determine if it's a directory.
            System.err.println(x);
            return false;
        }
    }
};

If the try-catch is NOT removed, may get something like:

Error:

|  exception java.io.IOException is never thrown in body of corresponding try statement

|          } catch (IOException x) {

|            ^----------------------...

One way to rewrite this snippet might be:

DirectoryStream.Filter<Path> filter =
     new DirectoryStream.Filter<Path>() {
     public boolean accept(Path file) {
        return Files.isDirectory(file);
     }
};
  • The sentences,

To match *, ?, or the other special characters, you can escape them by using the backslash character, .
For example: \ matches a single backslash, and ? matches the question mark."

appear to be missing the extra ""s to escape the characters, that is I believe they meant "\ matches a single backslash" for example,
and there is also an erroneous ", ." after "character" instead of "." .


In "Links, Symbolics and Otherwise", https://dev.java/learn/java-io/file-system/links/ (archived as is at https://web.archive.org/web/20260307205057/https://dev.java/learn/java-io/file-system/links/ )


In "Accessing Resources using Paths", https://dev.java/learn/java-io/file-system/file-path/ (archived as is https://web.archive.org/web/20260307205037/https://dev.java/learn/java-io/file-system/file-path/ )

  • In the sentence "Along with the factory methods from the Files, it gives you more features than the File class, and better performances, especially to access larger files and directories."
    "performances" should be "performance"

  • In "lastModified() et setLastModified(): return or set the time that this file was last modified." "et" was probably intended to be "and" or a comma

  • In "The Path class, introduced in the Java SE 7 release, is one of the primary entrypoint of the java.nio.file package." , "entrypoint" should be "entrypoints"


Thanks again for all this great content! I am still reviewing this series of articles so feel free to leave open even if you address the above (I would also be happy to confirm any changes before closing if desired).

Activity

  1. willy-b commented on Mar 9, 2026

    @willy-b
    Author

    Adding a few more items I found while continuing through this series (thanks again!).

    In the "Walking the File Tree" article, https://dev.java/learn/java-io/file-system/walking-tree/ (archived as is at https://web.archive.org/web/20260308193955/https://dev.java/learn/java-io/file-system/walking-tree/ ),

    • The following text is repeated 2x verbatim consecutively by the current authors:

    The visitFile() method is invoked for files. If you have specified the FOLLOW_LINKS option and your file tree has a circular link to a parent directory, the looping directory is reported in the visitFileFailed() method with the FileSystemLoopException. The following code snippet shows how to catch a circular link and is from the Copy example:

    Image
    • For the PrintFiles example, there is a missing space in the print output for the file size IO.println("(" + attr.size() + "bytes)"); (should be + " bytes)");),
    Image so the code as provided currently produces output like:
    [...]
    Regular file: /home/liveuser/Desktop/united_nations_universal_declaration_of_human_rights_article_27.png (88841bytes)
    Regular file: /home/liveuser/Desktop/united_nations_universal_declaration_of_human_rights_article_26.png (159165bytes)
    Regular file: /home/liveuser/Desktop/united_nations_universal_declaration_of_human_rights_article_12_right_to_privacy.png (81025bytes)
    Regular file: /home/liveuser/Desktop/un_udhr_article_18_19_20.png (150520bytes)
    [...]
    Regular file: /home/liveuser/Desktop/OECD_legal_0188_pg7-8_BasicPrivacyPrinciples_FIPPs.png (209289bytes)
    [...]
    Regular file: /home/liveuser/Desktop/ICESCR_article13_incl_13c.png (321444bytes)
    [...]
    Regular file: /home/liveuser/Desktop/junger_v_daley_1st_amendment_protections_for_computer_source_code.png (430858bytes)
    Regular file: /home/liveuser/Desktop/bernstein_vs_united_states_1st_amendment_protections_for_code.png (412052bytes)
    

    Where e.g. (159165bytes) would presumably be intended to be (159165 bytes).

    • In the sentence, "The Find example does precisely that. Find is similar to the UNIX find utility, but has pared down functionally." , "functionally" should be "functionality."

    • In the Copy example, the done() method at least has multiple issues identified so far

    Image
    • (1) "Number of files copied" count does not include subfolders: should NOT use non-recursive Files.list to count how many files were copied in a possibly recursive copy
      E.g. if one has folder ~/example with ~/example/a.txt, ~/example/b.txt, and ~/example/subfolder/c.txt ,
      then you will see the following obviously erroneous result using the code provided
    [liveuser@localhost-live ~]$ java Copy ~/example ~/example_copied -depth 2
    Copy directory: /home/liveuser/example
    Copy directory: /home/liveuser/example/subfolder
    Copy file: /home/liveuser/example/subfolder/c.txt
    Copy file: /home/liveuser/example/b.txt
    Copy file: /home/liveuser/example/a.txt
    Number of files copied: 2
    

    Where one can see "a.txt", "b.txt", and "c.txt" were copied, but it says "Number of files copied: 2"; and we can confirm the "c.txt" was copied:

    [liveuser@localhost-live ~]$ find example_copied
    example_copied
    example_copied/a.txt
    example_copied/b.txt
    example_copied/subfolder
    example_copied/subfolder/c.txt
    
    • (2) there is an unnecessarily complicated way to get the destination path in done()
      Since destination is of type Path, it seems unnecessary to do Paths.get(destination.toUri()) and in fact it seems to work fine skipping this and
      replacing Paths.get(destination.toUri()) with destination

    • In the Copy example, there is a comment "//log it and move" in an exception handler, which does not log anything

    • In the Copy example, if one wants particular permissions, ownership, ACL lists (if supported), or other user defined attributes (see https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/attribute/package-summary.html ) to hold on the copy that may not be guaranteed by the author's example. Java supports multiple platforms and the Files.copy documentation says, even if COPY_ATTRIBUTES is passed, the handing of the attributes varies by operating system ("The exact file attributes that are copied is platform and file system dependent and therefore unspecified. ", see https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/Files.html#copy(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption...) ) so in real world use that is a point I would worry about, and it might be worth noting that (someone might otherwise copy your tutorial in an inappropriate setting where attributes must be preserved during copies but are not done by Files.copy automatically on that platform). One will probably want to set any restrictive permissions/ACLs/ownership manually on each folder in the destination as it is created BEFORE copying any possibly sensitive files into it at a minimum. It seems the author went out of their way to manually copy the last modified timestamp attribute for each directory after processing it; and when setting up each folder in the destination they do copy with COPY_ATTRIBUTES (unclear why the author chose not to pass the option for the files, though on my Fedora 42 system POSIX permissions are copied even without explicitly passing COPY_ATTRIBUTES), but for anything sensitive one would probably want to double check in the code that permissions or other attributes of interest had been properly set on the folder and each file before proceeding (and ensure files/folders are not temporarily set to more permissive permissions during the process).

    • There may be other issues in the Copy example, I am still reviewing it.

    • I have NOT yet reviewed the Chmod example yet, I will report back after reviewing that code carefully and testing if I find anything.


    In the article "Watching a Directory for Changes", https://dev.java/learn/java-io/file-system/watching-dir-changes/ (archived as is at https://web.archive.org/web/20260308213037/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ):

    • In the sentences, "Save see the WatchDir example at the end of this section. Save it to your computer, and compile it.", the first sentence should probably start with "See." It appears the "Save" starting the second sentence got duplicated before the first sentence during the author's writing process, or something like that.
    • It's harmless but there is a typo is in one of the comments in the WatchDir example: "// ignore to keep sample readbale", where "readbale" should be "readable"
    • Will report back if I find anything after reviewing the WatchDir example code carefully and testing.

    Thanks!

  2. JosePaumard commented on Mar 9, 2026

    @JosePaumard
    Contributor

    "et" was probably intended to be "and" or a comma (I was probably tired when I wrote this ;))
    As for the Copy example. I did a quick and dirty fix. At least it is telling you what it did. Trying to count after the copy will fail if the directory was changed by another process in the meantime.
    These examples should probably be removed, along with the Chmod (I wouldn't spend too much time on it), which 1) is too complicated 2) was probably written 10+ years ago, and is now outdated.

  3. willy-b commented on Apr 11, 2026

    @willy-b
    Author

    Thanks! Almost all issues reported above appear fixed.

    Here are some more small items discovered while reading this series they may be of interest to address.


    In "Watching a Directory for Changes",
    https://web.archive.org/web/20260407195226/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ,
    under the heading "Retrieving the File Name", it currently says:

    The file name is retrieved from the event context. The previous example retrieves the file name with this code:

     for(WatchEvent<?> event: key.pollEvents()) {
     [...]
     WatchEvent<Path> ev = (WatchEvent<Path>)event;
     Path filename = ev.context();
    

    When you compile this example, it generates the following error:

    Note: Example.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.
    

    This error is a result of the line of code that casts the WatchEvent to a WatchEvent.

    It seems that "casts the WatchEvent to a WatchEvent" should instead be "casts the WatchEvent<?> to a WatchEvent" with a wildcard instead of a generic type here.

    And it seems to be an error that the code example shown is using a specific cast with a generic return type (generics are not used in the actual return, unlike in the full example later in the text):

    The WatchDir example avoids this error by creating a utility cast method that suppresses the unchecked warning, as follows:

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
       return (WatchEvent<Path>)event;
    }
    

    As written in the page, the above would not compile (note this is an example PRECEDING and separate from the full WatchDir example later, which does compile),
    with the following compile-time error:

    incompatible types: WatchEvent<Path> cannot be converted to WatchEvent<T>
            return (WatchEvent<Path>)event;
                   ^
      where T is a type-variable:
        T extends Object declared in method <T>cast(WatchEvent<?>)
    

    Instead, the cast method probably should either be implemented to be either consistently specific like

    @SuppressWarnings("unchecked")
    static WatchEvent<Path> cast(WatchEvent<?> event) {
        return (WatchEvent<Path>)event;
    }
    

    or consistently generic in the actual example later (see below):

       @SuppressWarnings("unchecked")
        static <T> WatchEvent<T> cast(WatchEvent<?> event) {
            return (WatchEvent<T>)event;
        }
    

    In the "Walking the File Tree" article, https://dev.java/learn/java-io/file-system/walking-tree/ (archived as it is at https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/ , archived as it was at https://web.archive.org/web/20260308193955/https://dev.java/learn/java-io/file-system/walking-tree/ ), most issues reported earlier have been resolved.

    Remaining issues are,

    For the Copy example ( https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/#copy ):

    • There is a comment "//log it and move" in an exception handler, which does not log anything (reported in previous comment, but it is not fixed yet)

    • In the visitFile method, Files.copy should probably be passed the COPY_ATTRIBUTES CopyOption as the example currently only does this for copying directories in preVisitDirectory (or consistently declare it is not copying owner/group , e.g. on Linux/MacOS setting to the current owner/group or on Windows dropping ACLs),
      but I would advise a comment noting that COPY_ATTRIBUTES does NOT guarantee all permissions/ownership/ACL or other relevant information will copied per Java API spec.

    Specifically, e.g.:

    Nonetheless, COPY_ATTRIBUTES is needed for e.g. OpenJDK to even attempt to copy file ownership information on Unix/Linux/MacOS (not mode, which is always copied as far as I know) and on Windows for security attributes to be attempted to be copied.

    I can give a simple example on Windows where another user ("otheruser") has been specifically denied access to a file,
    for which the parent directory of the containing directory has inheritable access grant to the other user "otheruser".
    When the dev.java Copy example is run to copy that file,
    the explicit access control setting to block "otheruser" from "some_file.txt" specifically, is lost and "otheruser" automatically gains access to the copy. (Note the ACLs overall used in this example are not recommended, just used for an example.)

    A preview of the result is that user otheruser cannot read the original file but can read the copy:

    [running as "otheruser"]
    C:\Users\liveuser\dev.java-examples>more example-to-copy\some_file.txt
    Cannot access file C:\Users\liveuser\dev.java-examples\example-to-copy\some_file.txt
    
    C:\Users\liveuser\dev.java-examples>more example-to-copy2\some_file.txt
    Some file
    
    Image

    Here is the full example:

    C:\Users\liveuser\dev.java-examples>icacls .
      LIVEMACHINE\otheruser:(X)
      LIVEMACHINE\otheruser:(RX)
      LIVEMACHINE\otheruser:(OI)(CI)(IO)(RX,GR,GE)
      NT AUTHORITY\SYSTEM:(OI)(CI)(F)
      BUILTIN\Administrators:(OI)(CI)(F)
      LIVEMACHINE\liveuser:(OI)(CI)(F)
      NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
      BUILTIN\Administrators:(I)(OI)(CI)(F)
      LIVEMACHINE\liveuser:(I)(OI)(CI)(F)
    
    Successfully processed 1 files; Failed processing 0 files
    
    C:\Users\liveuser\dev.java-examples>dir
     Volume in drive C is OS
     Volume Serial Number is 1111-1111
    
     Directory of C:\Users\liveuser\dev.java-examples
    
    04/09/2026  07:41 PM    <DIR>          .
    04/09/2026  07:41 PM    <DIR>          ..
    04/09/2026  06:43 PM             4,094 Copy$Replicator.class
    04/09/2026  06:43 PM             1,461 Copy.class
    04/09/2026  06:42 PM             4,582 Copy.java
    04/09/2026  07:21 PM    <DIR>          example-to-copy
                   3 File(s)         10,137 bytes
                   3 Dir(s)  14,576,103,424 bytes free
    
    C:\Users\liveuser\dev.java-examples>icacls example-to-copy
    example-to-copy LIVEMACHINE\otheruser:(I)(RX)
                    LIVEMACHINE\otheruser:(I)(OI)(CI)(IO)(RX,GR,GE)
                    NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                    BUILTIN\Administrators:(I)(OI)(CI)(F)
                    LIVEMACHINE\liveuser:(I)(OI)(CI)(F)
    
    Successfully processed 1 files; Failed processing 0 files
    
    C:\Users\liveuser\dev.java-examples>icacls example-to-copy\some_file.txt
    example-to-copy\some_file.txt LIVEMACHINE\otheruser:(DENY)(R,D)
                                  LIVEMACHINE\otheruser:(N)
                                  LIVEMACHINE\otheruser:(I)(RX)
                                  NT AUTHORITY\SYSTEM:(I)(F)
                                  BUILTIN\Administrators:(I)(F)
                                  LIVEMACHINE\liveuser:(I)(F)
    
    Successfully processed 1 files; Failed processing 0 files
    
    C:\Users\liveuser\dev.java-examples>java Copy example-to-copy example-to-copy2 -depth 1
    Copy directory: example-to-copy
    Copy file: example-to-copy\some_file.txt
    Number of files copied: 1
    
    C:\Users\liveuser\dev.java-examples>icacls example-to-copy2\some_file.txt
    example-to-copy2\some_file.txt LIVEMACHINE\otheruser:(RX)
                                   NT AUTHORITY\SYSTEM:(F)
                                   BUILTIN\Administrators:(F)
                                   LIVEMACHINE\liveuser:(F)
    
    Successfully processed 1 files; Failed processing 0 files
    

    From otheruser's perspective:

    C:\Users\liveuser\dev.java-examples>dir
     Volume in drive C is OS
     Volume Serial Number is 1111-1111
    
     Directory of C:\Users\liveuser\dev.java-examples
    
    04/09/2026  07:41 PM    <DIR>          .
    04/09/2026  07:41 PM    <DIR>          ..
    04/09/2026  06:43 PM             4,094 Copy$Replicator.class
    04/09/2026  06:43 PM             1,461 Copy.class
    04/09/2026  06:42 PM             4,582 Copy.java
    04/09/2026  07:21 PM    <DIR>          example-to-copy
                   3 File(s)         10,137 bytes
                   3 Dir(s)  14,550,806,528 bytes free
    
    C:\Users\liveuser\dev.java-examples>more example-to-copy\some_file.txt
    Cannot access file C:\Users\liveuser\dev.java-examples\example-to-copy\some_file.txt
    
    C:\Users\liveuser\dev.java-examples>dir
     Volume in drive C is OS
     Volume Serial Number is 1111-1111
    
     Directory of C:\Users\liveuser\dev.java-examples
    
    04/09/2026  07:43 PM    <DIR>          .
    04/09/2026  07:43 PM    <DIR>          ..
    04/09/2026  06:43 PM             4,094 Copy$Replicator.class
    04/09/2026  06:43 PM             1,461 Copy.class
    04/09/2026  06:42 PM             4,582 Copy.java
    04/09/2026  07:21 PM    <DIR>          example-to-copy
    04/09/2026  07:21 PM    <DIR>          example-to-copy2
                   3 File(s)         10,137 bytes
                   4 Dir(s)  14,550,347,776 bytes free
    
    C:\Users\liveuser\dev.java-examples>more example-to-copy2\some_file.txt
    Some file
    

    Showing "otheruser" can read "some_file.txt" in the copy but not the original.

    • In the current Copy program, ownership and group information also gets dropped on Linux (e.g. Fedora 42) (and I would expect the same on MacOS) (resolved if one adds COPY_ATTRIBUTES and has permission to set the group):
    [liveuser@localhost-live dev.java-examples]$ java Copy example-folder example-folder-copied -depth 1
    Copy directory: example-folder
    Copy file: example-folder/some_file
    Number of files copied: 1
    
    [liveuser@localhost-live dev.java-examples]$ tree
    .
    ├── Copy.java
    ├── example-folder
    │   └── some_file
    └── example-folder-copied
        └── some_file
    
    3 directories, 3 files
    [liveuser@localhost-live dev.java-examples]$ ls -lart example-folder
    total 0
    -rw-------. 1 liveuser somegroup      0 Apr  9 18:11 some_file
    drwxr-xr-x. 2 liveuser liveuser  60 Apr  9 18:11 .
    drwxr-xr-x. 4 liveuser liveuser 100 Apr  9 18:15 ..
    
    [liveuser@localhost-live dev.java-examples]$ ls -lart example-folder-copied/
    total 0
    drwxr-xr-x. 2 liveuser liveuser  60 Apr  9 18:11 .
    -rw-------. 1 liveuser liveuser   0 Apr  9 18:15 some_file
    drwxr-xr-x. 4 liveuser liveuser 100 Apr  9 18:15 ..
    

    Group "somegroup" has been quietly replaced with a different group "liveuser" during the copy. (If the Copy were run as a separate machine user, its ownership would replace the original owner and group, meaning a program using Copy without attributes under its own user would not be able to move another user's files around without possibly revoking their access, unless COPY_ATTRIBUTES were set. Though again, if the user does not have permission to set the original owner/group on the copy, it will continue without doing so in OpenJDK so should be double checked if important.)

    Readers might be advised that copying the attributes might be made a program parameter, logging whether they are being copied or not so users are not surprised, and if enabled, the program could check whether POSIX is supported and if so whether attributes and owner/group of the copied file match the original file and log a message or abort/rollback if the permissions could not be preserved. A similar check could be added for Windows.


    In the "Walking the File Tree" article, https://dev.java/learn/java-io/file-system/walking-tree/ (archived as it is at https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/ , archived as it was at https://web.archive.org/web/20260308193955/https://dev.java/learn/java-io/file-system/walking-tree/ ),

    For the Chmod example ( https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/#chmod )

    • There is a redundant check, in that
    // minimum is who and operator (u= for example)
            if (exprs.length() < 2)
                throw new IllegalArgumentException("Invalid mode");
    

    is redundant with the same check after the split

           for (String expr: exprs.split(",")) {
                // minimum of who and operator
                if (expr.length() < 2)
                    throw new IllegalArgumentException("Invalid mode");
    

    so the example could be updated to just keep the 2nd check.

    • The example could also check whether the file system and path supports POSIX permissions ( is Files.getFileAttributeView(path, PosixFileAttributeView.class) null?)
      and give the user an informative message if the system does not support POSIX/chmod operations, instead of waiting for an UnsupportedOperationException to be thrown.

    • Not strictly an error, but the comma-separated multiple mode behavior does not match the Linux chmod command in that the expressions are NOT applied in sequence and removals override adds regardless of order
      e.g.: the traditional "chmod" handles conflicting "=", "-" and "+" by applying them in order, "u+rwx,u-x,u-w,u+w"

    $ chmod ugo= example
    $ ls -lart example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 example
    $ chmod u+rwx,u-x,u-w,u+w example
    $ ls -lart example
    -rw-------. 1 liveuser liveuser 0 Apr 10 22:51 example
    

    Compare to the behavior of this Chmod:

    $ java -classpath ./app/build/classes/java/main org/example/Chmod ugo= /home/liveuser/example
    $ ls -lart /home/liveuser/example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    $ java -classpath ./app/build/classes/java/main org/example/Chmod u+rwx,u-x,u-w,u+w /home/liveuser/example
    $ ls -lart /home/liveuser/example
    -r--------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    

    An even simpler example is if one starts with revoking all permissions before granting just for the owner in the same command (e.g. "ugo=,u=rw" to clear all permissions then set read and write for the owner),
    which in this Chmod example will result in all permissions being revoked with no grant to the owner:

    $ java -classpath ./app/build/classes/java/main org/example/Chmod ugo=,u=rw /home/liveuser/example
    $ ls -lart /home/liveuser/example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    
    # compare to the original chmod
    $ chmod ugo=,u=rw example
    $ ls -lart example
    -rw-------. 1 liveuser liveuser 0 Apr 10 22:51 example
    

    Of course in this case the user should just use the simpler "go=,u=rw" instead and the Chmod example here will work in that case, but the actual chmod command gives a different result and does not require simplification to work.


    Thanks very much!

  4. JosePaumard commented on Apr 13, 2026

    @JosePaumard
    Contributor

    Hello, and thank you again for your report! I just pushed a fix.
    As for the examples in the Walking trees, I don't want to make them more complex than what they are. Deep diving into the complexity of file attributes, and the difference between POSIX and Windows is very interesting, but goes far beyond the scope of these examples, which is just to show how the API is working on not-too-trivial examples. Plus, they have been written about 10 years ago, and would probably need some attention.
    As for your report about the check on exp / expr that is redundant, are you sure it is?
    Again, thank you!

  5. willy-b commented on Apr 14, 2026

    @willy-b
    Author

    Re: https://dev.java/learn/java-io/file-system/walking-tree/#copy

    Thanks for making the main update regarding COPY_ATTRIBUTES recommended in my last comment vs the original author's implementation leaving that off the copying of files (only on directories):

    In the visitFile method, Files.copy should probably be passed the COPY_ATTRIBUTES CopyOption as the example currently only does this for copying directories in preVisitDirectory (or consistently declare it is not copying owner/group , e.g. on Linux/MacOS setting to the current owner/group or on Windows dropping ACLs)

    In https://web.archive.org/web/20260413125851/https://dev.java/learn/java-io/file-system/walking-tree/#copy
    compared to the previous version https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/#copy , Files.copy now has COPY_ATTRIBUTES in visitFile:
    Image

    I can confirm this fixes the simple Windows example I gave earlier (see the last comment from me):

    A preview of the result is that [unauthorized] user otheruser cannot read the original file but can read the copy:

    [running as "otheruser"]
    C:\Users\liveuser\dev.java-examples>more example-to-copy\some_file.txt
    Cannot access file C:\Users\liveuser\dev.java-examples\example-to-copy\some_file.txt
    
    C:\Users\liveuser\dev.java-examples>more example-to-copy2\some_file.txt
    Some file
    
    Image [fixed by your 2026-04-13 update]

    I still recommend an additional code comment with warnings that it remains unsafe (copy will proceed even if attributes cannot be copied without warnings or errors) so people know to enforce security attributes on Windows or group/ownership on Linux/MacOS some other way:

    but I would advise a comment noting that COPY_ATTRIBUTES does NOT guarantee all permissions/ownership/ACL or other relevant information will copied per Java API spec.
    [...]
    OpenJDK on Windows platforms unfortunately silently ignores any failure to copy security attributes or ACLs when performing Files.copy
    (Source: https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/windows/classes/sun/nio/fs/WindowsFileCopy.java#L255 ).
    OpenJDK on Linux and MacOS platforms also unfortunately silently ignores any failure to copy the ownership/group part of permissions attribute information during the copying of files (e.g. if the user running the Copy program does not have permission to set the group on the original file onto the copy).
    (Source: see exceptions are swallowed unless failIfUnableToCopyPosix is set, https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java#L666 noting that even when COPY_ATTRIBUTES is set, failIfUnableToCopyBasic is set but NOT failIfUnableToCopyPosix which is always false ( https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java#L415 )).


    In the Chmod example of https://dev.java/learn/java-io/file-system/walking-tree/#chmod (archived as is https://web.archive.org/web/20260413125851/https://dev.java/learn/java-io/file-system/walking-tree/#chmod )
    Re:

    As for your report about the check on exp / expr that is redundant, are you sure it is?

    Yes, I think you can safely remove the other author's check on exprs but should keep the check on expr. If exprs.length() < 2, expr.length() < 2 is necessarily true for all String expr: exprs.split(",") as expr are substrings of exprs (must be same length or shorter) so the condition will be caught there. There is no operation in between the checks that is unsafe for exprs.length() < 2 either, though none of the code (with or without the first check) is safe for a null exprs so you could add a null check in the first line of the method as a separate concern.

    Let us review the relevant block:

    public static Changer compile(String exprs) {
            // minimum is who and operator (u= for example)
            if (exprs.length() < 2)
                throw new IllegalArgumentException("Invalid mode");
    
            // permissions that the changer will add or remove
            final Set<PosixFilePermission> toAdd = new HashSet<PosixFilePermission>();
            final Set<PosixFilePermission> toRemove = new HashSet<PosixFilePermission>();
    
            // iterate over each of expression modes
            for (String expr: exprs.split(",")) {
                // minimum of who and operator
                if (expr.length() < 2)
                    throw new IllegalArgumentException("Invalid mode");
    

    The Set declarations/initializations don't affect exprs, so we can ignore them and see this as:

    // minimum is who and operator (u= for example)
            if (exprs.length() < 2)
                throw new IllegalArgumentException("Invalid mode");
           // [irrelevant code not affecting exprs]
           for (String expr: exprs.split(",")) {
                // minimum of who and operator
                if (expr.length() < 2)
                    throw new IllegalArgumentException("Invalid mode");
    

    If exprs is not null, then exprs is a non-null String and either contains a "," or does not.
    If exprs DOES NOT contain ",":

    • then exprs.split(",") will return an array containing a single element equal to exprs, and for (String expr: exprs.split(",")) { will execute once with expr.equals(exprs) == true (expr and exprs will contain the same text and have the same length).
    • expr.length() < 2 will have the same value as exprs.length() < 2, and it is redundant

    If exprs DOES contain ",":

    • exprs.split(",") will return an array containing more than one element, and each element will be a string shorter than exprs as they are substrings
    • if exprs.length() < 2 was true, then definitely expr.length() < 2 will be true as expr is a substring of exprs, so that exception will still be thrown without the first check
    • however, expr.length() < 2 still checks something new (expr.length() < 2 can be true without exprs.length() < 2) as each comma separated sub expression has a length requirement too, which is why I said the first check should be removed as redundant with the second (remove check on exprs length, NOT expr) though vice versa redundancy is not true

    Thanks: In the latest https://dev.java/learn/java-io/file-system/watching-dir-changes/
    ( https://web.archive.org/web/20260413132843/https://dev.java/learn/java-io/file-system/watching-dir-changes/
    vs previous version https://web.archive.org/web/20260407195226/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ), the following comments have been fixed:

    It seems that "casts the WatchEvent to a WatchEvent" should instead be "casts the WatchEvent<?> to a WatchEvent" with a wildcard instead of a generic type here.
    [...]
    And it seems to be an error that the code example shown is using a specific cast with a generic return type (generics are not used in the actual return, unlike in the full example later in the text)

    Image

    Thanks!

    Note that it still says "error" in two places there when describing a unchecked warning (not an error):

    it generates the following error[...]
    This error is a result of the line of code that casts[...]


    These changes are appreciated!

  6. willy-b commented on Apr 21, 2026

    @willy-b
    Author

    (Side note: I do not use ANY LLMs or AI in the review of dev.java or writing these issues or comments.)

  7. JosePaumard commented on Apr 22, 2026

    @JosePaumard
    Contributor

    Thank you again for your review! I just pushed a fix.
    As for the copy example: I think I understand that the person who wrote this example (15+ years ago) did not want to trigger the call to split() in case the passed argument is not correct. Calling split() is exepensive. What can be improved are the error messages.

  8. willy-b commented on Apr 23, 2026

    @willy-b
    Author

    Thanks!


    I can confirm in https://web.archive.org/web/20260422182728/https://dev.java/learn/java-io/file-system/walking-tree/ that the warning comment for the copy example requested is now present:

    Image

    // Be aware that COPY_ATTRIBUTES may omit copying
    // some attributes without warning.

    (beyond using COPY_ATTRIBUTES in Files.copy visitFile, I had requested also a warning that in sensitive cases it should be double checked with retry/rollback or other protections because
    COPY_ATTRIBUTES doesn't guarantee that it will succeed and can silently fail to copy security attributes while otherwise appearing to succeed, that is as explained previously

    OpenJDK on Windows platforms unfortunately silently ignores any failure to copy security attributes or ACLs when performing Files.copy
    (Source: https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/windows/classes/sun/nio/fs/WindowsFileCopy.java#L255 ).
    OpenJDK on Linux and MacOS platforms also unfortunately silently ignores any failure to copy the ownership/group part of permissions attribute information during the copying of files (e.g. if the user running the Copy program does not have permission to set the group on the original file onto the copy).
    (Source: see exceptions are swallowed unless failIfUnableToCopyPosix is set, https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java#L666 noting that even when COPY_ATTRIBUTES is set, failIfUnableToCopyBasic is set but NOT failIfUnableToCopyPosix which is always false ( https://github.com/openjdk/jdk/blob/988ec86dc6137d3798030a188bb9b214120a553d/src/java.base/unix/classes/sun/nio/fs/UnixFileSystem.java#L415 )).

    )

    I am assuming the following items I mentioned earlier in this file are ACK/WON'T FIX (fine by me just FYI):


    In https://web.archive.org/web/20260422182728/https://dev.java/learn/java-io/file-system/walking-tree/ , I don't see any changes to resolve what I reported earlier on the chmod example, nor did I see any ACK/"WON'T FIX" comment, so repeating here:

    (from my earlier comment at #210 (comment) , on the dev.java Chmod example, re older snapshot https://web.archive.org/web/20260407210613/https://dev.java/learn/java-io/file-system/walking-tree/ )

    Not strictly an error, but the comma-separated multiple mode behavior does not match the Linux chmod command in that the expressions are NOT applied in sequence and removals override adds regardless of order

    e.g.: the traditional "chmod" handles conflicting "=", "-" and "+" by applying them in order, "u+rwx,u-x,u-w,u+w"

    $ chmod ugo= example
    $ ls -lart example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 example
    $ chmod u+rwx,u-x,u-w,u+w example
    $ ls -lart example
    -rw-------. 1 liveuser liveuser 0 Apr 10 22:51 example
    

    Compare to the behavior of this Chmod:
    [running in a local Gradle build folder as was running some extra separate unit tests I wrote there is why it is having the special -classpath and folder, you can ignore as irrelevant; would usually be java Chmod ugo= /home/liveuser/example and java Chmod u+rwx,u-x,u-w,u+w /home/liveuser/example]

    $ java -classpath ./app/build/classes/java/main org/example/Chmod ugo= /home/liveuser/example
    $ ls -lart /home/liveuser/example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    $ java -classpath ./app/build/classes/java/main org/example/Chmod u+rwx,u-x,u-w,u+w /home/liveuser/example
    $ ls -lart /home/liveuser/example
    -r--------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    

    An even simpler example is if one starts with revoking all permissions before granting just for the owner in the same command (e.g. "ugo=,u=rw" to clear all permissions then set read and write for the owner),
    which in this Chmod example will result in all permissions being revoked with no grant to the owner:

    $ java -classpath ./app/build/classes/java/main org/example/Chmod ugo=,u=rw /home/liveuser/example
    $ ls -lart /home/liveuser/example
    ----------. 1 liveuser liveuser 0 Apr 10 22:51 /home/liveuser/example
    
    # compare to the original chmod
    $ chmod ugo=,u=rw example
    $ ls -lart example
    -rw-------. 1 liveuser liveuser 0 Apr 10 22:51 example
    

    Of course in this case the user should just use the simpler "go=,u=rw" instead and the Chmod example here will work in that case, but the actual chmod command gives a different result and does not require simplification to work.

    Feel free to ACK the above difference between the dev.java chmod example and the Unix/Linux chmod as "WON'T FIX / INTENDED" but just wanted to confirm with you before ignoring it.


    re: https://dev.java/learn/java-io/file-system/watching-dir-changes/

    In the latest version (archived as is at https://web.archive.org/web/20260422182917/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ),

    the "Retrieving the File Name" section is now free of errors (code block that would not have compiled was fixed, incorrect types in text were fixed, mention of errors that were warnings have been fixed) but it is now missing a sentence originally present explaining why the code block is being introduced.

    So what remains is to reintroduce the sentence before the code block saying

    The WatchDir example avoids this [warning] by creating a utility cast method that suppresses the unchecked warning, as follows

    right before

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
       return (WatchEvent<T>)event;
    }
    

    Thanks!

  9. JosePaumard commented on Apr 23, 2026

    @JosePaumard
    Contributor

    Thank you for your review! I just piushed a fix.
    As for the Copy and Chmod examples: as I told you, this is really old stuff, and I don't feel like touching them too much now. I think they need to be completely replaced by other, more focused and meaningful examples. There are too many things in these examples, and the knowledge required to understand them (as you pointed it BTW) goes way beyond the Java IO API elements they want to showcase. But that will take me too much time right now.

  10. willy-b commented on Apr 23, 2026

    @willy-b
    Author

    No problem at all re: the additional notes on Copy and Chmod examples.

    Other than that, I don't see the fix you mention pushed ( #210 (comment) ) showing at https://dev.java/learn/java-io/file-system/watching-dir-changes/ (archived as is right now at https://web.archive.org/web/20260423221638/https://dev.java/learn/java-io/file-system/watching-dir-changes/?no_cache=true , compare to previous https://web.archive.org/web/20260422182917/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ) (you could also send a pull request but I am not trying to change your team's process).

    That is, as mentioned in my previous comment I think a sentence dropped during your changes still needs to be reintroduced before the code block saying

    The WatchDir example avoids this [warning] by creating a utility cast method that suppresses the unchecked warning, as follows

    right before the (now fixed in an earlier update, see earlier in these comments) code block:

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
       return (WatchEvent<T>)event;
    }
    

    Thanks very much for your support!

  11. JosePaumard commented on Apr 24, 2026

    @JosePaumard
    Contributor

    Thank you for pointing this! From what I see, I think the fix is online now.

  12. willy-b commented on Apr 27, 2026

    @willy-b
    Author

    Thanks!


    In the latest version of https://dev.java/learn/java-io/file-system (archived as is at https://web.archive.org/web/20260426204428/https://dev.java/learn/java-io/file-system/?cache=false )


    In the latest version of https://dev.java/learn/java-io/file-system/file-path/ (archived as is at https://web.archive.org/web/20260426204506/https://dev.java/learn/java-io/file-system/file-path/?cache=false ),

    • FileStore.getTotalSpace() is duplicated in the table (still true, image from 2026-04-24 snapshot):
    Image
    • There should probably be a warning added to "setReadable"/"setWritable"/"setExecutable" noting these only update the owner's permissions by default (there is a 2-arg form that tries to update for others as well but does not allow fine-grained user, group, all control):

      setReadable(boolean), setWritable(boolean), setExecutable(boolean): allows you to change the corresponding security attribute of the file. These methods return true if the operation succeeded

      E.g. on Linux, using setReadable(false) on a file appears to make it inaccessible (cat /tmp/somefile gives cat: /tmp/somefile: Permission denied , but it can fail to remove the ability for other users to read it, even if the current user is the owner and could revoke all read permissions, so that another user can)

      liveuser@localhost-live:~$ echo "something to hide" > /tmp/somefile
      liveuser@localhost-live:~$ ls -lart /tmp/somefile
      -rw-r--r--. 1 liveuser liveuser 18 Apr 26 21:19 /tmp/somefile
      liveuser@localhost-live:~$ cat /tmp/somefile
      something to hide
      liveuser@localhost-live:~$ echo "use JShell to run file.setReadable(false) which unfortunately only hides from
      owner; should use POSIX attributes instead to remove group/all reader"
      use JShell to run file.setReadable(false) which unfortunately only hides from owner; should use POSIX attribut
      es instead to remove group/all reader
      liveuser@localhost-live:~$ cat /tmp/somefile
      cat: /tmp/somefile: Permission denied
      liveuser@localhost-live:~$ su otheruser
      Password:  
      otheruser@localhost-live:/home/liveuser$ cat /tmp/somefile
      something to hide
      

      Instead or in addition, this should probably recommend to use NIO POSIX File Permissions controls like Files.setPosixFilePermissions(java.nio.file.Path,java.util.Set) ( https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/Files.html#setPosixFilePermissions(java.nio.file.Path,java.util.Set) ) instead (or Files.setAttribute with POSIX or other permission attributes, like ACLs on some Windows setups as in the example in the Javadoc at https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/attribute/AclFileAttributeView.html ).

      (Cross-link: This is not to be confused with the example of Files.copy dropping ACLs on Windows and group/ownership info that you made updates for in response to the earlier comment, discussed in Typos and small issues in "File System Basics" articles of "The Java I/O API" tutorial ( https://dev.java/learn/java-io/file-system/ ) #210 (comment) , but also pertains to access.)

    • In the later java.io.File , java.nio.file comparison table, when giving the alternative for setExecutable, setReadable, setReadOnly, setWritable:

      The File methods that set various attributes: setExecutable(), setReadable(), setReadOnly(), setWritable() These methods are replaced by the Files method setAttribute(Path, String, Object, LinkOption...).

      NIO Files.setAttribute is mentioned (which is generally applicable) but not Files.setPosixFilePermissions which has the closest correspondence to those methods mentioned from java.io.File.

    • and in the java.io.File , java.nio.file comparison table there is also:

      File.list() and listFiles | Files.newDirectoryStream()

      which might mention that java.nio.file also has Files.list which returns a Stream instead of the DirectoryStream<Path> returned by Files.newDirectoryStream.


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is at https://web.archive.org/web/20260426222417/https://dev.java/learn/java-io/file-system/path/?cache=false )


    In the latest version of https://dev.java/learn/java-io/file-system/file-system/ (archived as is at https://web.archive.org/web/20260426230100/https://dev.java/learn/java-io/file-system/file-system/?cache=false )

    • In the sentences

      Remember that some drive letters may be used without the drive been mounted. The following code checks if every drive letters is readable.
      "drive been mounted" should be "drive having been mounted" and
      "letters" should be "letter".


    In the latest version of https://dev.java/learn/java-io/file-system/move-copy-delete (archived as is at https://web.archive.org/web/20260426231027/https://dev.java/learn/java-io/file-system/move-copy-delete/?cache=false ),


    In the latest version of https://dev.java/learn/java-io/file-system/metadata/ (archived as is at https://web.archive.org/web/20260427001610/https://dev.java/learn/java-io/file-system/metadata/?cache=false ),

    • In the first three lines

      Managing Files Attributes
      File and File Store Attributes
      A file system's metadata is typically referred to as its file attributes.

    it seems that "Managing Files Attributes" on the first line should be "Managing File Attributes" to be consistent with the very next two lines. See also e.g. the usage in the official Javadoc at https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/attribute/package-summary.html .


    Thanks very much again for all the excellent content! (And for providing a place to submit feedback to potentially improve it slightly for other readers by fixing small potentially distracting typos and the like.)

  13. JosePaumard commented on Apr 27, 2026

    @JosePaumard
    Contributor

    Thank you again for your review and report! I just pushed a fix.

  14. 9 remaining items

  15. willy-b commented on Jun 13, 2026

    @willy-b
    Author

    Thanks so much! I found a couple of more items while continuing to review these articles.


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is at https://web.archive.org/web/20260612010658/https://dev.java/learn/java-io/file-system/path/ )

    • The sentences

      If a path has no root component, then this path ends with the given path if it has more elements than the given path, and these elements are equal.
      If this path has a root component, then this path ends with the given path if the root component of this path ends with the root component of the given path, and the corresponding elements of both path are equal.

      seem a bit unclear, and both sentences seem to have errors.

      In the first sentence, at a minimum it seems that "if it has more elements than" should be "if it has at least as many elements as", and "these elements are equal" should be "the last N elements are equal where N is the length of the given path".

      In the second sentence, "If this path has a root component" should be "If the given/other path has a root component" (to complement the first sentence, which covers the case where the OTHER/given path does not have a root component), and "both path" should be "both paths".

      Compare to the documentation ( https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/Path.html#endsWith(java.nio.file.Path) )

      If the given path has N elements, and no root component, and this path has N or more elements, then this path ends with the given path if the last N elements of each path, starting at the element farthest from the root, are equal.

      If the given path has a root component then this path ends with the given path if the root component of this path ends with the root component of the given path, and the corresponding elements of both paths are equal.


    On the latest version of https://dev.java/learn/java-io/file-system/creating-reading-directories/ (archived as is at https://web.archive.org/web/20260612045659/https://dev.java/learn/java-io/file-system/creating-reading-directories/ )


    Thanks!

  16. JosePaumard commented on Jun 15, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  17. willy-b commented on Jun 19, 2026

    @willy-b
    Author

    Thanks, it looks like there were some regressions in the dev.java team's latest change (noting I do not get to review the changes the team will make in response to my comments before they go up, and I do not edit the pages myself) and the 2nd item reported above was not fixed yet.


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is at https://web.archive.org/web/20260619204037/https://dev.java/learn/java-io/file-system/path/ ) (I do not propose direct edits nor get a chance to review these before they make the changes)

    It appears there were some regressions that came in when the dev.java team split what was previously a combined description of startsWith/endsWith into separate "Does a Path Start with Another Path?" and "Does a Path End with Another Path?" sections in https://web.archive.org/web/20260619204037/https://dev.java/learn/java-io/file-system/path/ vs the previous https://web.archive.org/web/20260612010658/https://dev.java/learn/java-io/file-system/path/ .

    • In the "Does a Path Start with Another Path?" section

      Does a Path Start with Another Path?
      There are two overloads of the Path.startsWith() method. The first one takes a String argument: startsWith(String) and the second one takes a Path: endsWith(Path). The method that takes a String converts the String to a Path and then calls the other method.

      "the second one takes a Path: endsWith(Path)" should be "the second one takes a Path: startsWith(Path)", as they are still talking about overloads of "startsWith" in this rewritten version at this point (comparing the one that takes a String with the one that takes a Path).

      Then, in the parallel "Does a Path End with Another Path?" section below that,

      Does a Path End with Another Path?
      There are two overloads of the Path.startsWith() and Path.endsWith() methods.
      The first one takes a String argument: endsWith(String) and the second one takes a endsWith(String).

      "There are two overloads of the Path.startsWith() and Path.endsWith() methods. " should just be "There are also two overloads of the Path.endsWith() method",

      and "the second one takes a endsWith(String)" should be "the second one takes a Path: endsWith(Path)".


    This was reported in a comment above,
    but it is still the case in the latest version of https://dev.java/learn/java-io/file-system/creating-reading-directories/ (archived as is at https://web.archive.org/web/20260619205516/https://dev.java/learn/java-io/file-system/creating-reading-directories/ )

    that the link

    createTempDirectory(String, FileAttribute...)

    links to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/Files.html#createTempDirectory(java.nio.file.Path,java.lang.String,java.nio.file.attribute.FileAttribute...) when it should link to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/Files.html#createTempDirectory(java.lang.String,java.nio.file.attribute.FileAttribute...) .


    In the latest version of https://dev.java/learn/java-io/file-system/file-path/ (archived as is at https://web.archive.org/web/20260619222351/https://dev.java/learn/java-io/file-system/file-path/ )


    Thanks very much!

  18. JosePaumard commented on Jun 22, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  19. willy-b commented on Jun 24, 2026

    @willy-b
    Author

    Thanks for the fixes!

    https://dev.java/learn/java-io/file-system/path/ was almost cleared up but there is one item that was requested above that appears accidentally not yet fixed by the dev.java (see below).


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is https://web.archive.org/web/20260623134044/https://dev.java/learn/java-io/file-system/path/?no_cache=true )


    In the latest version of https://dev.java/learn/java-io/file-system/file-path/ (archived as is at https://web.archive.org/web/20260624172443/https://dev.java/learn/java-io/file-system/file-path/ )

    • the heading "Introducing the Legacy File Class" has a capital "L" in the text but a lowercase "l" in the sidebar

      [sidebar] Introducing the legacy File Class

      "legacy" should be "Legacy".


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is at https://web.archive.org/web/20260624141348/https://dev.java/learn/java-io/file-system/path/ ),


    In the latest version of https://dev.java/learn/java-io/file-system/path/ (archived as is at https://web.archive.org/web/20260624141348/https://dev.java/learn/java-io/file-system/path/ ),

    • In the sentence

      The following code used the first factory method to create a path.

      descriptions of the examples are not typically past tense in these articles so it may be better to say "The following code uses" rather than "used".

      For a first example, consider that a few sentences down before a diagram it is said:

      The examples in this section use the following directory structure.

      Or compare the sentence introducing the next code snippet after this one

      The following code snippet defines a Path instance and then invokes several methods to obtain information about the path:

      which uses the present tense "The following code snippet defines".


    Thanks!

  20. JosePaumard commented on Jun 25, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  21. willy-b commented on Jun 28, 2026

    @willy-b
    Author

    Thanks! Here are more potential items I found during continued review of this tutorial series, which you may be interested to fix.


    In the latest version of https://dev.java/learn/java-io/file-system/walking-tree/ (archived as is at https://web.archive.org/web/20260627193328/https://dev.java/learn/java-io/file-system/walking-tree/ )

     case END_DIRECTORY -> {
                           var res = visitor.postVisitDirectory(ev.file(), ev.ioeException());
    
                           // SKIP_SIBLINGS is a no-op for postVisitDirectory
                           if (res == FileVisitResult.SKIP_SIBLINGS)
                               res = FileVisitResult.CONTINUE;
                           yield res;
                       }
    
    Image
    • In the code snippet, on the line

      (if (dir.getFileName().toString().equals("SCCS")) {
      

      the parenthesis before "if" should be removed, so that "(if" becomes "if".

    Image
    • In the sentence

      If you are writing a recursive copy, you create the new directory in preVisitDirectory() before attempting to copy the files to it (in visitFiles()).

      "visitFiles()" should be "visitFile()" (remove errant trailing "s" and also have a hyperlink added to be consistent with "preVisitDirectory()").

    • In the sentence

      By default, walkFileTree() does not follow symbolic links.

      it would be consistent with most other method mentions in this article and useful to the reader to hyperlink "walkFileTree()" to one of its overloads, e.g. "walkFileTree()" ( https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/Files.html#walkFileTree(java.nio.file.Path,java.util.Set,int,java.nio.file.FileVisitor) ).

    • For the code snippet

      The following code snippet looks for files that end in .java or .class and prints those files to standard output:

      PathMatcher matcher =
      FileSystems.getDefault().getPathMatcher("glob:*.{java,class}");
      
      Path filename = ...;
      if (matcher.matches(filename)) {
       IO.println(filename);
      }
      

      "looks for files ... and prints those files" may be misleading as the code snippet just checks whether a single hardcoded filename matches the pattern or not.
      Maybe this should say "The following code snippet checks whether a particular path ends in .java or .class and prints it to standard output if it matches."


    In the latest version of https://dev.java/learn/java-io/file-system/file-path/ (archived as is at https://web.archive.org/web/20260627013953/https://dev.java/learn/java-io/file-system/file-path/ )


    In the latest version of https://dev.java/learn/java-io/file-system/path (archived as is at https://web.archive.org/web/20260627013956/https://dev.java/learn/java-io/file-system/path/ )

    • In the sentence

      It is important to note that normalize does not check at the file system when it cleans up a path.

      "not check at the file system" might be simplified to "not check the file system".

    • In the sentences

      In this example, the two paths share the same node, home. To navigate from home to bar, you first navigate one level down to sally and then one more level down to bar. Navigating from bar to home requires moving up two levels

      in "share the same node, home", the word "home" is not styled as the path parts are styled (and even "home") in the rest of the example.
      See the image below:

    Image

    In the latest version https://dev.java/learn/java-io/file-system/move-copy-delete/ (archived as is at https://web.archive.org/web/20260628012008/https://dev.java/learn/java-io/file-system/move-copy-delete/?no_cache=true )

    • In the sentences

      You can copy a file or directory by using the copy(Path, Path, CopyOption...) method. [...]
      When copying a symbolic link, the target of the link is copied.
      If you want to copy the link itself, and not the contents of the link, specify either the NOFOLLOW_LINKS or REPLACE_EXISTING option.

      one can get the impression that when copying a link to a file,

      specifying the REPLACE_EXISTING CopyOption (LinkOption implements CopyOption) alone

      will cause the link at the source Path to be copied instead of the linked/target file of the source link (like specifying NOFOLLOW_LINKS ) .

      But this is not the case. REPLACE_EXISTING will allow the target path to be overridden even if it is a link to somewhere else (without following THAT link),

      but will not prevent copying the file specified by a link at the source Path instead of the link at the source Path itself (unless NOFOLLOW_LINKS is added).

      Same at

      REPLACE_EXISTING – Performs the copy even when the target file already exists. If the target is a symbolic link, the link itself is copied (and not the target of the link).

      "If the target is a symbolic link, the link itself is copied" should be "If the target is a symbolic link, the link itself is replaced" ("copied" should be "replaced" in the context of "REPLACE_EXISTING").

      I think there is confusion due to the fact that when the source Path is a link, one refers to the path specified by the link as the link "target"
      and then this method also takes two Paths, a "source" and a "target". So there are a couple of different possible meanings of "target" that can come up in this example.
      When the documentation is discussing the "target" parameter of the copy method (which is overridden when REPLACE_EXISTING is specified,
      and if the "target" parameter Path itself specifies a link, that link is overridden, not its own link target) that can become confused with whether the source parameter Path, if it is a link, is resolved to the path that link specifies (the source Path link target).


    In the latest version of https://dev.java/learn/java-io/file-system/listing/ (archived as is at https://web.archive.org/web/20260627184612/https://dev.java/learn/java-io/file-system/listing/ )

    • In the sentence

      The class that implements the DirectoryStream interface also implements Iterable, so you can iterate through the directory stream, reading all of the objects.

      it might be more precise to replace "The class that implements the DirectoryStream interface also implements Iterable" with "The DirectoryStream interface extends Iterable" (or "Iterable is a superinterface of DirectoryStream").

    • In the sentences

      Note that if there is an exception during directory iteration then DirectoryIteratorException is thrown with the IOException as the cause. Iterator methods cannot throw exceptions.

      "Iterator methods cannot throw exceptions" should be "Iterator methods cannot throw checked exceptions" (which is why DirectoryIteratorException, a subclass of RuntimeException (unchecked) must wrap IOException (a checked Exception)).

      In one of the code examples further down on the same page, both a direct IOException (could be thrown by the opening of resources) and a DirectoryIteratorException are caught

      } catch (IOException | DirectoryIteratorException x) {

      but in a subsequent example only IOException is caught, which might be unintentional.

    • In the code snippet, the one line comment

      // In this snippet, it can // only be thrown by newDirectoryStream.
      

      seems to have a second "//" by accident. It could be split into 2 lines or just have the 2nd inline "//" removed.


    In the latest version of https://dev.java/learn/java-io/file-system/watching-dir-changes/ (archived as is at https://web.archive.org/web/20260627225209/https://dev.java/learn/java-io/file-system/watching-dir-changes/ ),

    • In the sentences

      Process the pending events for the key. You fetch the List of WatchEvent objects from the pollEvents() method.

      The link to "pollEvents()" points to the WatchService, erroneously linking to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/WatchService.html#pollEvents() [wrong]
      when the method belongs to the WatchKey, it should link to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/nio/file/WatchKey.html#pollEvents() [correct].
      That is "WatchService.html" should be "WatchKey.html" in the URL.

    • In the WatchDir example, in the main method, after

      if (args[0].equals("-r")) {
               if (args.length < 2)
                   usage();
               recursive = true;
               dirArg++;
      }
      

      it would complete the input validation to add an else if aborting with a usage message when there is more than 1 argument if the first argument is not -r:

      } else if (args.length > 1) {
         // if usage is `java WatchDir dir` there should not be any more arguments after `dir`, the only permissible 2 arg case is `java WatchDir -r dir`
         usage(); // will exit the program after printing "usage: java WatchDir [-r] dir"
      }
      

      otherwise someone can do e.g. java WatchDir dir -r without error but without the intended behavior.


    Thanks so much!

  22. JosePaumard commented on Jun 29, 2026

    @JosePaumard
    Contributor

    Thank you for your report! A fix is on its way.
    As for the last example. This is the kind of legacy example I really don't like, where managing the errors of a command line becomes more important than what the example is supposed to show. You can have dirs named -r...

  23. willy-b commented on Jul 4, 2026

    @willy-b
    Author

    Thanks very much!

    I am re-reviewing everything and looking for any other regressions but I see the following right away in those latest changes and so want to let you know.


    In the latest version of https://dev.java/learn/java-io/file-system/walking-tree/ (archived as is in https://web.archive.org/web/20260704171655/https://dev.java/learn/java-io/file-system/walking-tree/ )

    Thanks for the last update by the dev.java team!
    The code was updated to match the description (instead of changing the description as suggested),
    which is fine, but there are two issues:

    1. there is a typo in that "filtrer" should be "filter"
    2. now that the Path variable is not just a filename, but a full path,
      if the "root" is a path with multiple parts,
      it will fail as written, so either a .map(p -> p.getFileName()) should be added after .list(root) or the glob pattern should use a double star "**" instead of "*" to match including the leading slashes/directories .

    What the dev.java team wrote, with comments added on what needs to change to work as described:

    PathMatcher matcher =
       FileSystems.getDefault().getPathMatcher("glob:*.{java,class}"); 
      // [above IS NOT multipart path safe;  replacing "*" with "**" is one solution to make this multipart path safe ]
    
    Path root = ...;
    Files.list(root) // [alternatively, if not using "**" above, can add `.map(p -> p.getFileName())` here]
        .filtrer(matcher::matches) // [typo by dev.java team "filtrer" instead of "filter"
        .forEach(IO::println);
    

    In the latest version of https://dev.java/learn/java-io/file-system/file-path/ (archived as is at https://web.archive.org/web/20260704171824/https://dev.java/learn/java-io/file-system/file-path/ )

    • In the text

      understanding the File may still be important to you

      it may have been intended to be "understanding the File class may still be important to you." OR "understanding File may still be important to you." (either update "File" to "File class" or remove "the") Same for

      As a legacy class, the File does not expose all the security attributes offered by your file system.

      where "the File" should probably also be "the File class" or "File" .

      These were reported in my last comment as well but I am repeating them as they were not yet fixed or ack'd as of https://web.archive.org/web/20260704171824/https://dev.java/learn/java-io/file-system/file-path/ .

    • For setPosixFilePermissions(path,permissions) next to the legacy setReadable/setWritable/setExecutable, "allow" should be "allows" here (unlike the preceding ones I asked for "allow" as they had plural subjects, but this is one method).
      You may also consider though to add a note there that it is a Files method (non-legacy, NIO) provided for comparison, or as a preferred alternative, to avoid confusion as the rest of the list is intended to be legacy methods. (This was mentioned in the last comment too but has not yet been fixed or ack'd as of https://web.archive.org/web/20260704171824/https://dev.java/learn/java-io/file-system/file-path/ . )


    In the latest version of https://dev.java/learn/java-io/file-system/watching-dir-changes/ (archived as is at https://web.archive.org/web/20260704172620/https://dev.java/learn/java-io/file-system/watching-dir-changes/ )

    In the changes made by the dev.java team in response to the last comment,

    it appears a condition which is not following best practices for conditionals was introduced:

    if (args.length == 0 ||
              args.length == 2 && !args[0].equals("-r") ||
              args.length > 2) {
              usage();
          }
    

    No error will result from this particular condition (perhaps surprisingly), but it is not a safe practice generally to mix "&&" and "||" pairs in a compound condition without grouping.

    As a best practice and to improve readability, I would humbly suggest that args.length == 2 && !args[0].equals("-r") should be grouped with parentheses (args.length == 2 && !args[0].equals("-r")).

    The approach of not grouping when mixing &&,|| is NOT safe generally
    since e.g. (A || B) && (C || D) when ungrouped also becomes A || B && C || D,
    where A=false,B=false,C=false,D=true satisfies the ungrouped but not the grouped version in that case, and there are many such cases where one can easily get into trouble if not grouping.
    Thus IMHO it is harder on the readers of your code if you do not group when mixing &&,||, even if in the particular case it would be safe.


    Thanks so much!

  24. JosePaumard commented on Jul 6, 2026

    @JosePaumard
    Contributor

    Thanks again you for your report! A fix is on its way.
    Also, as for your report "As a legacy class, the File does not expose all the security attributes". I double checked, and the fix (... File class does not expose...) is indeed on the live site. I wonder if there could be a sync issue somewhere?

  25. JosePaumard commented on Jul 11, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.
    Isn't it the same fix as the one in the other thread?

  26. willy-b commented on Jul 12, 2026

    @willy-b
    Author

    Yes, the comment #210 (comment) appeared on a different issue first briefly but it belongs here so I marked the other occurrence (of just the comment, not the whole issue) as a duplicate.

    Thanks for resolving #210 (comment) in the latest version of https://dev.java/learn/java-io/file-system/move-copy-delete/ (archived as is at https://web.archive.org/web/20260712070754/https://dev.java/learn/java-io/file-system/move-copy-delete/ ) .

    The items reported above were either resolved (or you acknowledged them as "Won't Fix"), so I am marking this closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions