1. Overview

When we work with files in Java, we often need to handle filenames. For example, sometimes we want to get the name without the extension from a given filename. In other words, we want to remove the extension of a filename.

In this tutorial, we’ll discuss the generic way to remove the extension from a filename.

2. Scenarios of Removing the Extension From a Filename

When we take a first look at it, we may think that removing the extension from a filename is a pretty easy problem.

However, if we take a closer look at the problem, it could be more complicated than we thought.

First of all, let’s have a look at the types a filename can be:

  • Without any extension, for example, “baeldung”
  • With a single extension, this is the most usual case, for example, “baeldung.txt
  • With multiple extensions, like “baeldung.tar.gz
  • Dotfile without an extension, such as “*.baeldung*“
  • Dotfile with a single extension, for instance, “*.baeldung.conf*“
  • Dotfile with multiple extensions, for example, “*.baeldung.conf.bak*“

Next, we’ll list expecting results of the examples above after removing the extension(s):

  • baeldung“: The filename doesn’t have an extension. Therefore, the filename should not be changed, and we should get “baeldung
  • baeldung.txt“: This is a straightforward case. The correct result is “baeldung
  • baeldung.tar.gz“: This filename contains two extensions. If we want to remove only one extension, “baeldung.tar” should be the result. But if we want to remove all extensions from the filename, “baeldung” is the correct result
  • “*.baeldung“: Since this filename doesn’t have any extension either, the filename shouldn’t be changed either. Thus, we’re expecting to see “.baeldung*” in the result
  • “*.baeldung.conf“: The result should be “.baeldung*“
  • “*.baeldung.conf.bak“: The result should be “.baeldung.conf” if we only want to remove one extension. Otherwise, “.baeldung*” is the expected output if we’ll remove all extensions

In this tutorial, we’ll test if the utility methods provided by Guava and Apache Commons IO can handle all the cases listed above.

Further, we’ll also discuss a generic way to solve the problem of removing the extension (or extensions) from a given filename.

3. Testing the Guava Library

Since version 14.0, Guava has introduced the Files.getNameWithoutExtension() method. It allows us to remove the extension from the given filename easily.

To use the utility method, we need to add the Guava library into our classpath. For example, if we use Maven as the build tool, we can add the Guava dependency to our pom.xml file:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

First, let’s have a look at the implementation of this method:

public static String getNameWithoutExtension(String file) {
   ...
   int dotIndex = fileName.lastIndexOf('.');
   return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
 }

The implementation is pretty straightforward. If the filename contains dots, the method cuts from the last dot to the end of the filename. Otherwise, if the filename doesn’t contain a dot, the original filename will be returned without any change.

Therefore, Guava’s getNameWithoutExtension() method won’t work for dotfiles without an extension. Let’s write a test to prove that:

@Test
public void givenDotFileWithoutExt_whenCallGuavaMethod_thenCannotGetDesiredResult() {
    //negative assertion
    assertNotEquals(".baeldung", Files.getNameWithoutExtension(".baeldung"));
}

When we handle a filename with multiple extensions, this method doesn’t provide an option to remove all extensions from the filename:

@Test
public void givenFileWithoutMultipleExt_whenCallGuavaMethod_thenCannotRemoveAllExtensions() {
    //negative assertion
    assertNotEquals("baeldung", Files.getNameWithoutExtension("baeldung.tar.gz"));
}

4. Testing the Apache Commons IO Library

Like the Guava library, the popular Apache Commons IO library provides a removeExtension() method in the FilenameUtils class to quickly remove the filename’s extension.

Before we have a look at this method, let’s add the Apache Commons IO dependency into our pom.xml:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>

The implementation is similar to Guava’s getNameWithoutExtension() method:

public static String removeExtension(final String filename) {
    ...
    final int index = indexOfExtension(filename); //used the String.lastIndexOf() method
    if (index == NOT_FOUND) {
      return filename;
    } else {
    return filename.substring(0, index);
    }
}

Therefore, the Apache Commons IO’s method won’t work with dotfiles either:

@Test
public void givenDotFileWithoutExt_whenCallApacheCommonsMethod_thenCannotGetDesiredResult() {
    //negative assertion
    assertNotEquals(".baeldung", FilenameUtils.removeExtension(".baeldung"));
}

If a filename has multiple extensions, the removeExtension() method cannot remove all extensions:

@Test
public void givenFileWithoutMultipleExt_whenCallApacheCommonsMethod_thenCannotRemoveAllExtensions() {
    //negative assertion
    assertNotEquals("baeldung", FilenameUtils.removeExtension("baeldung.tar.gz"));
}

5. Removing the Extension(s) From a Filename

So far, we’ve seen utility methods for removing the extension from a filename in two widely used libraries. Both methods are pretty handy and work for the most common cases.

However, on the other hand, they have some shortcomings:

  • They won’t work for dotfiles, for example, “*.baeldung*“
  • When a filename has multiple extensions, they don’t provide an option to remove the last extension only or all extensions

Next, let’s build a method to cover all cases:

public static String removeFileExtension(String filename, boolean removeAllExtensions) {
    if (filename == null || filename.isEmpty()) {
        return filename;
    }

    String extPattern = "(?<!^)[.]" + (removeAllExtensions ? ".*" : "[^.]*$");
    return filename.replaceAll(extPattern, "");
}

We added a boolean parameter removeAllExtensions to provide the option to remove all extensions or only the last extension from a filename.

The core part of this method is the regex pattern. So let’s understand what does this regex pattern do:

  • “(?<!^)[.]” – We use a negative-lookbehind in this regex. It matches a dot “*.*” that is not at the beginning of the filename
  • “*(?<!^)[.].**” – If the removeAllExtensions option is set, this will match the first matched dot until the end of the filename
  • “*(?<!^)[.][^.]*$*” – This pattern matches only the last extension

Finally, let’s write some test methods to verify if our method works for all different cases:

@Test
public void givenFilenameNoExt_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals("baeldung", MyFilenameUtil.removeFileExtension("baeldung", true));
    assertEquals("baeldung", MyFilenameUtil.removeFileExtension("baeldung", false));
}

@Test
public void givenSingleExt_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals("baeldung", MyFilenameUtil.removeFileExtension("baeldung.txt", true));
    assertEquals("baeldung", MyFilenameUtil.removeFileExtension("baeldung.txt", false));
}

@Test
public void givenDotFile_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals(".baeldung", MyFilenameUtil.removeFileExtension(".baeldung", true));
    assertEquals(".baeldung", MyFilenameUtil.removeFileExtension(".baeldung", false));
}

@Test
public void givenDotFileWithExt_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals(".baeldung", MyFilenameUtil.removeFileExtension(".baeldung.conf", true));
    assertEquals(".baeldung", MyFilenameUtil.removeFileExtension(".baeldung.conf", false));
}

@Test
public void givenDoubleExt_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals("baeldung", MyFilenameUtil.removeFileExtension("baeldung.tar.gz", true));
    assertEquals("baeldung.tar", MyFilenameUtil.removeFileExtension("baeldung.tar.gz", false));
}

@Test
public void givenDotFileWithDoubleExt_whenCallFilenameUtilMethod_thenGetExpectedFilename() {
    assertEquals(".baeldung", MyFilenameUtil.removeFileExtension(".baeldung.conf.bak", true));
    assertEquals(".baeldung.conf", MyFilenameUtil.removeFileExtension(".baeldung.conf.bak", false));
}

6. Conclusion

In this article, we’ve talked about how to remove extensions from a given filename.

First, we discussed the different scenarios of removing extensions.

Next, we’ve introduced the methods provided by two widely used libraries: Guava and Apache Commons IO. They are pretty handy and work for common cases but cannot work for dotfiles. Also, they don’t provide an option to remove a single extension or all extensions.

Finally, we built a method to cover all requirements.

As always, the full source code of the article is available over on GitHub.