Pages

Test on build with Maven

Let's add some testing to the tiny mavenized Java app seen in the previous couple of posts.

Actually, the app is so tiny that there is that there's nothing to test. This is easily solved, adding a method to the Main class
public static String getLoggerClassName() {
    return LOG.getClass().getName();
}
I plan to use JUnit 5 (aka Jupiter) as testing framework, asserting via Hamcrest, so I add both these dependencies to the POM.
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.6.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>
As I said, I plan to use JUnit and Hamcrest for testing only. I don't want to bother the user with them. That's the reason why I signaled to Maven that their scope is test.

Now I write a test for the newly added method
@Test
void getLoggerClassName() {
    String actual = Main.getLoggerClassName();
    assertThat(actual, is("ch.qos.logback.classic.Logger"));
}
I can run it from Eclipse in the usual way, anything works fine.

I build the app with Maven, clean package, and I get both my jars, slim and fat, as before. In both cases without the test dependencies, and that's good. Still, something is missing. Tests are not executed in this phase. Bad. I'd like to check anything works fine when I build my jar.

The issue is in the Maven Surefire Plugin. By default, an ancient version is executed that do not work nicely with JUnit 5. So, I force Maven to use a newer one. It's name is a bit scary, having an M in its version number. M as Milestone. However, it looks like we have to live with it. So I use it.
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M4</version>
</plugin>

Actually, there's no need to specify the group id in this case, since maven plugins is the default. Your choice.

That's it. Now the Maven package build trigger also the execution of the tests. Nice.

I have pushed the full code on GitHub. So, please, check there for details.

Go to the full post

Jar with dependencies through Maven

I'm creating a Java desktop app, a very simple one, see the previous post if you don't believe me, using Maven as build manager. The second step here is adding a dependency, and let Maven build a "fat" jar including them, to ease its deployment.

Say that I just want to use Logback classic in my application. I search for it on MvnRepository, I stay away from the alpha version so I go for 1.2.3.

Given that, modify my pom.xml is mostly a matter of copy and paste. Just remember that any dependency belongs to the dependencies element.
<project ...>
    <!-- ... -->
    <dependencies>
     <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
     </dependency>
    </dependencies>
</project>
Now I can Logback, and I'm going to do it through SLF4J.
import org.slf4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        // ...
        LOG.info("Hello");
    }
}
I run my application in Eclipse, everything goes as expected, getting as console output the required log
14:32:12.986 [main] INFO simple.Main - Hello
Now I run a maven build, as done in the previous post, setting the goals to clean package, and generate a jar.

I run it from the shell in the usual way
java -cp simple-0.0.1-SNAPSHOT.jar simple.Main
And I get a nasty feedback
Exception in thread "main" java.lang.NoClassDefFoundError:
    org/slf4j/LoggerFactory at simple.Main.(Main.java:7)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
    at java.base/jdk.internal.loader....
    ...
    ... 1 more
However, this is an expected behavior. Maven generates by default a "slim" jar, avoiding to include in it the dependencies. The user has to specify in the classpath where Java has to find them.

When we want to create a "fat" jar, we should say to Maven explicitly to do it. We do that with the Maven Assembly Plugin.

Maven, please, when you build this project, use the maven-assembly-plugin, version 3.3, when packaging. And, remember to build also a "fat" jar with all the dependencies!
<build>
 <plugins>
  <plugin>
   <artifactId>maven-assembly-plugin</artifactId>
   <version>3.3.0</version>
   <executions>
    <execution>
     <phase>
      package
     </phase>
     <goals>
      <goal>single</goal>
     </goals>
    </execution>
   </executions>
   <configuration>
    <descriptorRefs>
     <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
   </configuration>
  </plugin>
 </plugins>
</build>
Now, when I run the Maven build step, I get both slim and fat jar, here is how I run the latter
java -cp simple-0.0.1-SNAPSHOT-jar-with-dependencies.jar simple.Main
It works fine, so I push the changes on github.

Go to the full post

Simple Maven Eclipse Project

What I'm about to do here is creating a simple desktop app using Apache Maven as build automation tool.

To follow me, ensure you have on your machine:
  1. Java JDK - I use version 11, the currently latest available LTS, but the app is so simple that virtually any JDK will do
  2. Eclipse IDE - Actually, on my machine I have Spring Tool Suite (STS) 4.6, but again, the job is so basic, that almost any Eclipse-based IDE would be alright

From Eclipse, I select the "Maven Project" wizard from "File | New | Project ...", there I go for creating a simple project, skipping archetype selection. Then I have to specify a group and artifact id for my stuff. I enter "dd" as group id and "simple" as artifact id.

The other fields have a good default - so I keep them as suggested and I just push the "Finish" button.

Since I used a very minimal approach, I'm not surprised by the warning in my project. I go straight to my pom.xml to complete it.

I want to set the character set used in my source code, using UTF-8 (strictly not a necessity, still a good idea), and, most importantly, I want to set both the source and target compiler to JDK 11.
I do that specifying these properties inside the project element:
<properties>
    <project.build.sourceEncoding>
        UTF-8
    </project.build.sourceEncoding>
    <maven.compiler.source>
        11
    </maven.compiler.source>
    <maven.compiler.target>
        11
    </maven.compiler.target>
</properties>
Here you can see the full pom.xml.

Theoretically, Eclipse should trigger a project rebuild as you save a change in the Maven configuration file. Unfortunately, this is not what often happens. However, you could force the job by pushing Alt-F5, or right-clicking on the project name in the Project Explorer, then select Maven | Update Project...

I add a plain vanilla class, simple.Main, with a main function to perform the usual hello job and I have the first version of my application.

Now I'd like to use maven to generate my app jar. To do that, I have to specify which goals my maven build target should achieve. I right click on the project folder in the Eclipse Package Explorer, then in the Run as menu I select the "Maven build ..." item. As goals I specify "clean package", meaning that I want to clean everything up, so to recompile all source files and then package them in a jar file.

Now I can run this target. In the console window I see the feedback from Maven until I get the final result, a jar, named in my case simple-0.0.1-SNAPSHOT.jar, has been put in the project target directory.

To see if it works as I expect, I open a shell, go to the target directory, and there I execute the jar:
java -cp simple-0.0.1-SNAPSHOT.jar simple.Main
I'm happy to get back the expected hello message, so I put the project on Github.

Go to the full post