Exercise 1.2: Customizing the Hello, World! Application

Author

Chuck Nelson

Published

September 8, 2025

1 Customizing the Hello, World! Application

Run the following command in your terminal. The clean command removes all old files, forcing a fresh build.

mvn clean install

The warning should now be gone from the build output!

1.1 Customizing the Hello, World! Application

Now that our build is clean and reproducible, let’s make our application more dynamic. Instead of saying “Hello, World!”, we’ll modify it to greet a person by name, using a command-line argument.

The Goal We will update our App.java file to check for a command-line argument. If one is provided, it will use that as the name; otherwise, it will default to “World”.

Step 1: Modify App.java

Open src/main/java/com/example/helloworld/App.java and update the existing main method with the following code.

Note

Notice that after you declared the name variable, that VS Code has a warning about The value of the local variable name is not used. If you do not see the PROBLEMS tab, hit Ctrl+Shift+M to show it. That warning displayed in the VS Code PROBLEMS tab will automaticially go away when you use the variable later in the main method.

public class App {
  /**
   * Main method that prints Hello World or a personalized greeting.
   * @param args Command-line arguments
   */
  public static void main(String[] args) {
    String name;
    if (args.length > 0) {
      name = args[0];
    } else {
      name = "World";
    }
    System.out.println("Hello, " + name + "!");
  }
}
Tip

Did you notice the Intellisense technology inside of VS Code offering you code completions as you typed? You may have noticed the offered code was different than the example above. You can use either one. Often the offered Intellisense code completion is better, and follows code conventions and best practices, but not always. Remember, it is just a tool, to help you develop faster, not a replacement for human thinking and good, rational design.

Step 2: Run the Application from the Command Line

To see the change in action, you can run the application using the Maven Exec Plugin.

First, make sure your code is compiled:

Tip

If you are still on the PROBLEMS tab, hit Ctrl+` to switch to the TERMINAL tab.

mvn clean compile
Tip

If you find your terminal getting cluttered, hit Ctrl+L to clear the terminal.

Then, run your application with a command-line argument:

mvn exec:java -Dexec.mainClass="com.example.helloworld.App" -Dexec.args="Jane"

The output should be: Hello, Jane!

Now try running it without an argument:

mvn exec:java -Dexec.mainClass="com.example.helloworld.App"

The output should now be: Hello, World!

1.2 Update build tests

Step 3: Update AppTest.java

Finally, we need to update our unit test to ensure it correctly tests both cases. We’ll add two separate tests for this.

Open src/test/java/com/example/helloworld/AppTest.java and update the existing test by adding these two new methods:

@Test
public void testAppPrintsCorrectOutputWithArgument() {
  String expectedOutput = "Hello, TestUser!\n";
  App.main(new String[]{"TestUser"});
  assertEquals(expectedOutput, outContent.toString());
}

@Test
public void testAppPrintsCorrectOutputWithoutArgument() {
  String expectedOutput = "Hello, World!\n";
  App.main(new String[]{});
  assertEquals(expectedOutput, outContent.toString());
}
Note

Notice there are new problems in the VS Code PROBLEMS tab. Review those problems and see if you can identify the cause of the problem. That’s right! These two new methods are using an undeclared variable. this is a common issue encountered when pasting code from elsewhere. Let’s fix it.

Add two new variables in the AppTest class for outContent and originalOut.

    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    private final PrintStream originalOut = System.out;

The outContent variable is of type ByteArrayOutputStream. It is part of the java.io package and a member of the java.base module Java standard libraries. You can see it has already been imported into the AppTest.java file so the type is in scope for our code to use. Read more about the ByteArrayOutputStream in the Java documentation.

The originalOut variable is of type PrintStream. This is a streaming object that is a wrapper around the computer systems’ stdout file descriptor. It is logicially bound by the operating system to the default terminal display.

See:

  • https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/PrintStream.html
  • https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html

In order to use the PrintStream wrapper around stdout, you’ll need to setup the AppTest class to setup and restore the stream before each test. Update the class and add these two methods near the beginning of the class.

    @BeforeEach
    public void setUpStreams() {
        System.setOut(new PrintStream(outContent));
    }

    @AfterEach
    public void restoreStreams() {
        System.setOut(originalOut);
    }
Note

Did you notice the @BeforeEach, @AfterEach, and @Test strings before the methods in the source code? These are called Annotations. The purpose of a Java annotation is to provide metadata—extra information—about a program’s code that is not part of the code’s functionality itself. Annotations, which begin with an @ symbol, can be read and processed at compile time, during deployment, or at runtime.

In Java, specifically within the JUnit 5 testing framework, @Test, @BeforeEach, and @AfterEach are annotations used to define the lifecycle and behavior of test methods.

  • @Test: This annotation marks a method as a test method that JUnit should execute. When JUnit runs tests, it identifies all methods annotated with @Test and executes them as individual test cases.

  • @BeforeEach: This annotation indicates that the annotated method should be executed before each test method (@Test) in the current test class. It is commonly used for setting up a fresh, isolated environment for each test, such as initializing objects, creating test data, or configuring mock objects. This ensures that each test runs with a consistent starting state, preventing test interdependencies.

  • @AfterEach: This annotation indicates that the annotated method should be executed after each test method (@Test) in the current test class. It is typically used for cleaning up resources or resetting the environment after a test has completed, such as closing connections, deleting temporary files, or resetting application state. This helps in maintaining a clean state for subsequent tests and prevents resource leaks. In summary:

@Test defines the actual test case.

@BeforeEach provides a mechanism for setting up the environment before each test.

@AfterEach provides a mechanism for cleaning up the environment after each test. This structure promotes test isolation and maintainability by ensuring that each test runs independently and without side effects from previous tests.

Step 4: Run the Tests Again

Run your tests to verify that everything works as expected:

mvn test

You should see both tests pass, confirming that your application correctly handles both scenarios. The output will be similar to this.

[INFO] Scanning for projects...
[INFO] 
[INFO] -----------------< com.example.helloworld:hello-world >-----------------
[INFO] Building hello-world 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ hello-world ---
[INFO] skip non existing resourceDirectory /home/chuck/PSCC/Courses/CITC_1310-Programming_I/JavaProjects/JavaToolchain/hello-world/src/main/resources
[INFO] 
[INFO] --- compiler:3.11.0:compile (default-compile) @ hello-world ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- resources:3.3.1:testResources (default-testResources) @ hello-world ---
[INFO] skip non existing resourceDirectory /home/chuck/PSCC/Courses/CITC_1310-Programming_I/JavaProjects/JavaToolchain/hello-world/src/test/resources
[INFO] 
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ hello-world ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- surefire:3.1.2:test (default-test) @ hello-world ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.helloworld.AppTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.021 s -- in com.example.helloworld.AppTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.456 s
[INFO] Finished at: 2025-09-08T15:48:22-04:00
[INFO] ------------------------------------------------------------------------
Back to top