Mastering Filename Manipulation: How to Get a Filename Without Extension in Java

Extracting a filename without its extension is a common task in Java development, used in scenarios like batch file processing, report generation, asset management, and data organization. While the task seems straightforward, handling edge cases (e.g., hidden files, multiple dots, trailing extensions) can introduce bugs if not implemented carefully.

This blog explores four reliable methods to achieve this goal, including manual string manipulation, using popular third-party libraries, and leveraging Java's built-in NIO API. We’ll also cover best practices, performance comparisons, and edge case handling to ensure robust implementation.

Table of Contents#

  1. Common Scenarios for Filename Extension Removal
  2. Method 1: Manual String Manipulation 2.1 Basic Implementation 2.2 Handling Edge Cases
  3. Method 2: Apache Commons IO Library 3.1 Setup and Dependencies 3.2 Core Methods 3.3 Example Usage
  4. Method 3: Java NIO (Paths and Files) 4.1 Introduction to Java NIO 4.2 Extracting Filename Without Extension
  5. Method 4: Google Guava Library 4.1 Adding Dependency 4.2 Core Utilities
  6. Best Practices
  7. Performance Comparison
  8. Conclusion
  9. References

1. Common Scenarios for Filename Extension Removal#

  • Batch Processing: Renaming hundreds of files to remove extensions before conversion.
  • Report Generation: Using the base filename to generate related output files (e.g., report.pdfreport.csv).
  • Asset Management: Organizing media files by their base name across different storage systems.
  • Configuration Handling: Reading hidden system files (e.g., .bashrc) without modifying their names.

2. Method 1: Manual String Manipulation#

The most lightweight approach is to use Java’s built-in string methods. This requires no external dependencies but careful edge case handling.

2.1 Basic Implementation#

The core idea is to find the last occurrence of the . character and extract the substring before it:

public static String getBaseNameBasic(String fileName) {
    if (fileName == null || fileName.isEmpty()) {
        return fileName;
    }
    int lastDotIndex = fileName.lastIndexOf('.');
    return lastDotIndex == -1 ? fileName : fileName.substring(0, lastDotIndex);
}

Limitation: This fails for edge cases like hidden files (.bashrc) or files with trailing dots (file.).

2.2 Handling Edge Cases#

To cover all edge cases, extend the code to:

  • Ignore leading dots (hidden files).
  • Handle trailing dots (e.g., file.file).
  • Respect multiple dots (e.g., report.v1.2.pdfreport.v1.2).
public static String getBaseNameRobust(String fileName) {
    if (fileName == null || fileName.isEmpty()) {
        return fileName;
    }
 
    int lastDotIndex = fileName.lastIndexOf('.');
    // Case 1: No dot present
    if (lastDotIndex == -1) {
        return fileName;
    }
    // Case 2: Leading dot (hidden file like .bashrc)
    if (lastDotIndex == 0) {
        return fileName;
    }
    // Case 3: Trailing dot (file. → file)
    if (lastDotIndex == fileName.length() - 1) {
        return getBaseNameRobust(fileName.substring(0, fileName.length() - 1));
    }
    // Case 4: Normal or multi-dot filenames
    return fileName.substring(0, lastDotIndex);
}

Test Cases:

System.out.println(getBaseNameRobust("report.pdf")); // report
System.out.println(getBaseNameRobust(".bashrc")); // .bashrc
System.out.println(getBaseNameRobust("file.")); // file
System.out.println(getBaseNameRobust("report.v1.2.pdf")); // report.v1.2

3. Method 2: Apache Commons IO Library#

Apache Commons IO is a widely used library that provides pre-built, tested utilities for file operations, including filename handling.

3.1 Setup and Dependencies#

Add the dependency to your project:

  • Maven:
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.15.1</version>
    </dependency>
  • Gradle:
    implementation 'commons-io:commons-io:2.15.1'

3.2 Core Methods#

The FilenameUtils class includes two key methods:

  • getBaseName(String filename): Returns the filename without its extension, handling all edge cases.
  • getExtension(String filename): Complementary method to extract the extension.

3.3 Example Usage#

import org.apache.commons.io.FilenameUtils;
 
public class CommonsIoExample {
    public static void main(String[] args) {
        System.out.println(FilenameUtils.getBaseName("report.pdf")); // report
        System.out.println(FilenameUtils.getBaseName(".bashrc")); // .bashrc
        System.out.println(FilenameUtils.getBaseName("file.")); // file
        System.out.println(FilenameUtils.getBaseName("/home/user/report.v1.2.pdf")); // report.v1.2
    }
}

Key Advantage: Automatically handles all edge cases without custom code.


4. Method 3: Java NIO (Paths and Files)#

Java 7 introduced the NIO API, which provides a modern, cross-platform way to handle file paths and systems.

4.1 Introduction to Java NIO#

The Path class represents file system paths, and Paths.get() parses strings into path objects. While NIO doesn’t have a built-in method to remove extensions directly, we can combine path parsing with string manipulation.

4.2 Extracting Filename Without Extension#

import java.nio.file.Path;
import java.nio.file.Paths;
 
public class NioExample {
    public static String getBaseNameUsingNio(String filePath) {
        Path path = Paths.get(filePath);
        Path fileNamePath = path.getFileName();
        if (fileNamePath == null) {
            return filePath;
        }
        String fileName = fileNamePath.toString();
        int lastDotIndex = fileName.lastIndexOf('.');
        
        if (lastDotIndex == -1 || lastDotIndex == 0) {
            return fileName;
        }
        if (lastDotIndex == fileName.length() - 1) {
            return getBaseNameUsingNio(fileName.substring(0, fileName.length() - 1));
        }
        return fileName.substring(0, lastDotIndex);
    }
 
    public static void main(String[] args) {
        System.out.println(getBaseNameUsingNio("/home/user/docs/report.pdf")); // report
        System.out.println(getBaseNameUsingNio(".bashrc")); // .bashrc
    }
}

Alternative: Combine NIO with Apache Commons IO for cleaner code:

String baseName = FilenameUtils.getBaseName(path.getFileName().toString());

5. Method 4: Google Guava Library#

Google Guava is another popular utility library with robust filename handling capabilities.

5.1 Adding Dependency#

  • Maven:
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>32.1.3-jre</version>
    </dependency>
  • Gradle:
    implementation 'com.google.guava:guava:32.1.3-jre'

5.2 Core Utilities#

Guava’s Files class provides helper methods:

import com.google.common.io.Files;
 
public class GuavaExample {
    public static void main(String[] args) {
        System.out.println(Files.getNameWithoutExtension("report.pdf")); // report
        System.out.println(Files.getNameWithoutExtension("/home/user/report.v1.2.pdf")); // report.v1.2
        System.out.println(Files.getNameWithoutExtension(".bashrc")); // .bashrc
        System.out.println(Files.getNameWithoutExtension("file.")); // file
    }
}

Key Feature: Files.getNameWithoutExtension() automatically resolves the last path component before removing the extension.


6. Best Practices#

  1. Prefer Libraries Over Manual Code: Use Apache Commons IO or Guava to avoid reinventing the wheel and ensure edge case coverage.
  2. Handle Null/Empty Inputs: Always validate inputs to prevent NullPointerException or unexpected behavior.
  3. Respect Hidden Files: Never strip leading dots from filenames like .gitignore or .bashrc.
  4. Use Java NIO for Paths: For full file paths, parse them with Path objects to handle cross-platform path separators.
  5. Document Edge Case Behavior: Clearly comment how your code handles trailing dots, multiple dots, and hidden files.

7. Performance Comparison#

For most applications, performance differences are negligible. Below are rough estimates for 1 million iterations:

MethodTime (ms)
Manual String Ops~15
Apache Commons IO~25
Java NIO~20
Google Guava~22

Manual string manipulation is fastest, but libraries offer better readability and maintainability.


8. Conclusion#

Choosing the right method depends on your project’s requirements:

  • No Dependencies: Use manual string manipulation (with edge case handling) or Java NIO.
  • Existing Library Usage: If your project already uses Apache Commons IO or Guava, leverage their built-in utilities.
  • Cross-Platform Paths: Use Java NIO for robust path parsing.

All methods can handle edge cases when implemented correctly, so prioritize readability and maintainability unless performance is critical.


9. References#