A lightweight and flexible Java library to pretty-print directory structures — ideal for documentation, project overviews, or CLI tools.
Supports various options to customize the directories scanning and rendering:
- Filtering & sorting files and folders
- Emojis as file icons 🎉
- Limit displayed children of a folder (fixed value or dynamically)
- Custom line extension (comment, file details, etc.)
- Compact directory chains
- Maximum scanning depth
- Various styles for tree rendering
Note
JFileTreePrettyPrinter is perfect to explain your project structure!
See ProjectStructure.java to read the code that generated the tree from the below picture.
Important
Complete documentation available in wiki.
- Why use JFileTreePrettyPrinter?
- Requirements
- Import dependency
- Basic usage
- Customization options
- Project Information
Unlike a plain recursive Files.walk(), this library:
- Prints visually appealing directory trees.
- Allows rich customization (filters, sorts, emojis, compacting, tree style).
- Supports dynamic child limits and custom extensions per line.
- Is dependency-free (on runtime) and compatible with Java 21+.
- Java 21 or later
- No runtime dependencies
For Maven, import this dependency to your pom.xml:
<dependency>
<groupId>io.github.computerdaddyguy</groupId>
<artifactId>jfiletreeprettyprinter</artifactId>
<version>0.1.0</version>
</dependency>For Gradle:
implementation "io.github.computerdaddyguy:jfiletreeprettyprinter:0.1.0"// Example: BasicUsage.java
var printer = FileTreePrettyPrinter.createDefault(); // Create a printer with default options
var tree = printer.prettyPrint("src/example/resources/base"); // Pretty print the target folder
System.out.println(tree); // Admire the result!Result:
base/
├─ businessPlan.pdf
├─ businessProject.pdf
├─ cars/
│ ├─ Ferrari.doc
│ └─ Porsche.doc
├─ diyIdeas.docx
├─ giftIdeas.txt
└─ images/
├─ funnyCat.gif
├─ holidays/
│ ├─ meAtTheBeach.jpeg
│ └─ meAtTheZoo.jpeg
└─ landscape.jpeg
Note
In case of error while reading directories or files, an UncheckedIOException is thrown.
Files and directories can be selectively included or excluded using a custom PathMatcher.
Filtering applies independently to files and directories. Files are filtered only if their parent directory passes the directory filter. If none of a directory’s children match, the directory is still displayed.
The PathMatchers class provides several ready-to-use methods for creating and combining common matchers.
// Example: Filtering.java
var excludeDirWithNoJavaFiles = PathMatchers.not(PathMatchers.hasNameEndingWith("no_java_file"));
var hasJavaExtension = PathMatchers.hasExtension("java");
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(
options -> options
.filterDirectories(excludeDirWithNoJavaFiles)
.filterFiles(hasJavaExtension)
)
.build();filtering/
├─ dir_with_java_files/
│ ├─ file_B.java
│ └─ file_E.java
├─ dir_with_nested_java_files/
│ └─ nested_dir_with_java_files/
│ ├─ file_G.java
│ └─ file_J.java
└─ file_A.java
Files and directories can be sorted using a custom comparator (default is alphabetical order).
If the provided comparator considers two paths equal (i.e., returns 0), an alphabetical comparator is applied as a tie-breaker to ensure consistent results across all systems.
The PathSorts class provides a set of basic, ready-to-use comparators, as well as a builder for creating your own tailor-made sort.
// Example: Sorting.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.sort(PathSorts.DIRECTORY_FIRST))
.build();sorting/
├─ c_dir/
│ └─ c_file
├─ d_dir/
│ ├─ d_b_dir/
│ │ └─ d_b_file
│ └─ d_a_file
├─ a_file
├─ b_file
├─ x_file
└─ y_file
You can choose to use default built-in emojis, or define your own emoji mapping. Folders use the 📂 emoji, and files will have an emoji depending on their extension (when applicable).
// Example: Emojis.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withDefaultEmojis()) // or withEmojis(EmojiMapping) for custom mapping
.build();// Run Emojis.java example for the full list of emoji mappings
📂 emojis/
├─ 📦 file.zip
├─ 🐳 Dockerfile
├─ 🤵 Jenkinsfile
├─ ☕ file.java
├─ 📖 readme
├─ ⚙️ file.ini
├─ 📊 file.xlsx
├─ 📃 file.docx
├─ 📕 file.pdf
├─ 🎵 file.mp3
├─ 🖼️ file.jpeg
└─ 🎬 file.avi
You can set a fixed limit to the number of children displayed for each directory. Each directory and file that pass the filter (if set) counts for one.
// Example: ChildLimitStatic.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withChildLimit(3))
.build();child_limit_static/
├─ file_0_1
├─ folder_1/
│ ├─ file_1_1
│ ├─ file_1_2
│ ├─ file_1_3
│ └─ ...
├─ folder_2/
│ ├─ file_2_1
│ ├─ file_2_2
│ ├─ file_2_3
│ └─ ...
└─ ...
Or you can also set a limitation function, to dynamically choose the number of children displayed in each directory.
This avoids cluttering the result with known large folders (e.g. node_modules) while continuing to pretty-print other folders normally.
Use the ChildLimits class to help you build the limit function that fits your needs.
// Example: ChildLimitDynamic.java
var childLimit = ChildLimits.builder()
.setDefault(ChildLimits.UNLIMITED) // Unlimited children by default
.add(PathMatchers.hasName("node_modules"), 0) // Do NOT print any children in "node_modules" folder
.build();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withChildLimit(childLimit))
.build();child_limit_dynamic/
├─ file_0_1
├─ folder_1/
│ ├─ file_1_1
│ ├─ file_1_2
│ ├─ file_1_3
│ ├─ file_1_4
│ └─ file_1_5
└─ node_modules/
└─ ...
You can extend each displayed path with additional information by providing a custom Function<Path, String>.
This is useful to annotate your tree with comments, display file sizes, or add domain-specific notes.
The function receives the current path and returns an optional string to append (empty string is authorized).
If the function returns null, nothing is added.
Use the LineExtensions class to help you build line extension functions.
// Example: LineExtension.java
var printedPath = Path.of("src/example/resources/line_extension");
Function<Path, String> lineExtension = LineExtensions.builder()
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain"), "\t\t\t// All domain code: value objects, etc.")
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra"), "\t\t\t// All infra code: database, email service, etc.")
.add(PathMatchers.hasNameMatchingGlob("*.properties"), "\t// Config file")
.build();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withLineExtension(lineExtension))
.build();line_extension/
└─ src/
└─ main/
├─ java/
│ ├─ api/ // All API code: controllers, etc.
│ │ └─ Controller.java
│ ├─ domain/ // All domain code: value objects, etc.
│ │ └─ ValueObject.java
│ └─ infra/ // All infra code: database, email service, etc.
│ └─ Repository.java
└─ resources/
└─ application.properties // Config file
Directory chains with a single child directory are fully expanded by default, but you can inline them into a single tree entry.
// Example: CompactDirectories.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withCompactDirectories(true))
.build();single_directory_child/
├─ file1
├─ file2
└─ this/is/single/directory/child/
├─ file1
├─ file2
└─ file3
You can customize the default max depth (default is 20).
// Example: MaxDepth.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withMaxDepth(3))
.build();max_depth/
└─ level1/
├─ file1#1
├─ file1#2
└─ level2/
├─ file2#1
├─ file2#2
└─ level3/
└─ ... (max depth reached)
Choose between different built-in tree formats, or create your own.
The default is UNICODE_BOX_DRAWING, supported by all terminals, but you can also switch to use CLASSIC_ASCII.
// Example: FileTreeFormat.java
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withTreeFormat(TreeFormats.CLASSIC_ASCII))
.build();tree_format/
|-- file_1
|-- file_2
`-- subFolder/
|-- subFile_1
`-- subFile_2
- See 🆕CHANGELOG.md for a list of released versions and detailed changes.
- See 🗺️ROADMAP.md to discover planned features and upcoming improvements.
- This project is licensed under the Apache License 2.0. See ⚖️LICENSE for details.
- For any questions or feedback please open an issue on this repository, as detailed in 🤝CONTRIBUTING.md.
Made with ❤️ by ComputerDaddyGuy
