Core Java

NetBeans 9 Early Access

Java 9 is around the corner, and so is NetBeans 9. In this article we shall see the support that NetBeans 9 Early Access provides to developers to build Java 9 compatible applications.

Java 9 provides many (around 90) new features including Modules and JShell, a Read-Eval-Print-Loop (REPL), among many others. You may seek more information in the references at the end of this article and especially this link on NetBeans 9 support to JDK 9.

In this article we will learn how to:

  1. download and build Java 9 EA from sources
  2. download and build NetBeans 9 EA from sources
  3. use JShell REPL from NetBeans 9 EA
  4. create and build modules and dependencies among them using NetBeans 9 EA to make our life easier

1. Build OpenJDK 9 EA

OpenJDK is the reference implementation of any JDK Enhancement Proposals (JEPs) or even Java Specification Requests (JSRs). You might have many reasons why you want to build it from sources and don’t download one of the pre-built binaries, e.g.:

  • company policy that enforces you to build open source projects from source
  • you wish to get access to the latest early access OpenJDK
  • you want a build for your special platform

Of course you can proceed with this article by downloading a pre-built binary. But if you want to learn how to build JDK 9 then continue reading. Building your own JDK image provides a number of advantages, e.g. you don’t need to install anything or modify e.g. the Windows registry or the Java  Preferences pane on MacOS destroying your current default JDK (see e.g. this article). The executables will always be created in the same location, and by updating your repository and re-building the sources you can always have the latest version.

You can download the latest OpenJDK 9 binaries from this page. Sources can be found in the Mercurial repositories. E.g. OpenJDK 9 repos are here; project Jigsaw repo contains the hitting edge of the modular implementation, if you wish to be in the hitting edge. Below, you get some tips on how to build OpenJDK 9.

  1. Clone the JDK 9 Master mercurial repository.
  2. Read the README or README-builds.html file for more instructions.
  3. Next step before you start building is to execute: bash get_source.sh or ./get_source.sh if your shell is already bash. It is very important, after each update to call this command. The reason is that hg update only updates the master repository. OpenJDK consists of a number of mercurial repositories which need to be updated, too. This can be done by the get_source.sh command.
  4. ./configure --disable-warnings-as-errors
  5. sudo make all

The above commands build the latest version of OpenJDK. If you wish to build an earlier version of OpenJDK you need to follow these tips:

  1. hg up [tag]  e.g. jdk-9+147
  2. cd corba
  3. hg up [tag]  e.g. jdk-9+147
  4. Repeat steps 2 & 3 for directories: hotspot, jaxp, jaxws, jdk, langtools, nashorn
  5. ./configure --disable-warnings-as-errors
  6. sudo make clean
  7. sudo make all

The binaries are created in build/<platform_dir>/jdk, e.g. if you use a Mac in build/macosx-x86_64-normal-server-release/jdk.

2. Build and configure NetBeans 9 EA

You may download the latest NetBeans with JDK 9 support from http://wiki.netbeans.org/JDK9Support or build it from sources:

  1. hg clone http://hg.netbeans.org/main
  2. cd main
  3. hg clone http://hg.netbeans.org/main/contrib
  4. cd ..
  5. ant

Binary is created in nbbuild/netbeans. Configure it to run with JDK 8 or JDK 9 EA (etc/netbeans.conf). For jshell to be enabled, you need to setup NetBeans 9 with JDK 9 EA, though. So edit etc/netbeans.conf to point to the JDK 9 EA you built in Step 1:

netbeans_jdkhome="<path to OpenJDK 9 EA>/build/<platform_dir>/jdk"

Take a backup of this file, because the next time you build NetBeans it will be overriden and you will have to do this modification again.

Start netbeans by issuing the command: bin/netbeansor bin\netbeans.exe depending on your platform. Register the latest JDK 9 EA build as a Java Platform in NetBeans by means of Tools | Java Platforms | Add Platform (see Fig. 1) and select the OpenJDK 9 EA you built in Step 1.

Fig. 1 – Add JDK 9 EA Platform to NetBeans 9 EA

3. JShell support in NetBeans 9 EA

If you started NetBeans 9 with a JDK 9 implementation, then you can access JShell from the menu Tools | Open Java Platform Shell. JShell works the same as from the command line and additionally the NetBeans shortcuts work with it (e.g. sout --> (tab)). You may read more about JShell in the resources.

Fig. 2 - JShell in NetBeans 9 EA
Fig. 2 – JShell in NetBeans 9 EA.

4. Modules support in NetBeans 9 EA

NetBeans 9 EA provides a number of goodies to facilitate you with your modular projects. Before you continue, you must setup JDK 9 EA Platform in Tools | Java Platforms | Add Platform and select the path to OpenJDK 9 EA binaries you built in Step 1.

To transform an existing project into a module you need to perform two tasks:

  1. Setup the project to be JDK 9 compatible in Project Properties:
    1. In Libraries set Java Platform to your JDK 9 EA Java platform (Fig. 3).
    2. In Sources set Source/Binary Format to JDK 9 (Fig. 4).
  2. Add a Java Module Info (i.e. a module descriptor module-info.java) in your project (see Fig. 5):
    1. File | New File…| Java (category) | Java Module Info (File type)

Fig. 3 - Set Java Platform to JDK 9 EA
Fig. 3 – Set Java Platform to JDK 9 EA

Fig. 4 - Java Sources
Fig. 4 – Set Java Sources to JDK 9

Fig. 5 - Add a Java Module Info
Fig. 5 – Add a Java Module Info

module-info.java must always be in the root of a Java project in NetBeans 9. This is a restriction in NetBeans 9 and not of JDK 9. The only exception where we can have more than one module-info.java files in a single Java project is when we have unit tests. You can add module-info.java files inside Test Packages.

But let’s learn about NetBeans 9 EA modules support by implementing the quick start guide of project Jigsaw in NetBeans 9 EA.

4.1. My first modular application with NetBeans 9

This first example is a module named com.greetings that simply prints “Greetings!”. The module consists of two source files: the module declaration (module-info.java) and the main class.

By convention (a module name can be a Java-qualified identifier), the source code for the module is in a directory that is the name of the module (in our case com.greetings) — even though this is not necessary.

Create a new Java project in NetBeans by following these steps:

  1. File | New Project…
  2. Select Java (Category) and Java Application (Project) and click on Next
  3. In the next page select a Project Location and enter “com.greetings” as the project name because of the convention followed by the tutorial but a normal Java Project name like “Greetings” could also have been used. Rename the Main Class to be com.greetings.Main. Click Finish.

You should see a Java Project named com.greetings and inside it a class com.greetings.Main that contains a main() method. Modify it like so:

com.greetings.Main

package com.greetings;

/** @author javacodegeeks */
public class Main {
  /** @param args the command line arguments */
  public static void main(String[] args) {
    System.out.println("Greetings!");
  }
}

To transform the Java Project into a module, add a module-info.java as described previously, i.e. by right-clicking on the project name and selecting File | New File…| Java (category) | Java Module Info (File type).

An empty module-info.java is created in the root package of the project. Rename it as follows to be in accordance with the Jigsaw tutorial:

com.greetings.module-info.java

module com.greetings {
}

You must clean and build the project for the renaming of the module to take effect and run it with success. You should see the message “Greetings!” in the Output window.
With NetBeans you don’t need to care about command line syntax and arguments of javacand java commands. These are taken care by NetBeans IDE.

4.2. Adding dependencies

The second example updates the module declaration to declare a dependency on module org.astro. Module org.astro exports the API package org.astro.

Create a new Java project named org.astro by following the steps of the previous sub-chapter. Don’t create a Main Class this time. Once the project is created, right-click onto it and select New | Java Class… Enter World as the class name and org.astro as the package name and click Finish. Update the newly created class like so:

org.astro.World.java

package org.astro;

/** @author javacodegeeks */
public class World {
    public static String name() {
        return "world";
    }
}

Add a module-info.java like we did before for com.greetings.

org.astro.module-info.java

module org.astro {
}

Don’t forget to clean and build for the module renaming to take effect.

Now we need to add a dependency from com.greetings module to org.astro module in order to use its method World.name(). But before that, org.astro must export the package that contains this method. Both actions need to be made in module-info.java files of the two modules. NetBeans 9 provides you with useful hints on how to do that.

  1. Open org.astro‘s module-info.java and inside the brackets type Ctrl-Space. A popup menu appears that displays the available commands as shown in Fig. 6. Select exports and continue by typing org.astro which is the package name to export. NetBeans provides you hints as you type. Once you save module-info.java you will notice that the lock icon of the package in the Projects tab changes to an open lock.
  2. Open com.greetings‘s module-info.java and inside the brackets follow the steps described above to enter the command requires org.astro, this time referring to the module name and not the package name (another reason why the convention chosen by the quick start guide of project Jigsaw is not that successful as it is confusing to distinguish between package and module names). However, NetBeans complains with an error message module not found.  In order for NetBeans to be able to locate the module, one more step is needed.
  3. Right-click on Libraries folder of com.greetings Java project and select Add Project from the popup menu. Select the org.astro Java Project and click on Add Project JAR Files. The error is gone. Another way to do the same thing is to right-click on project com.greetings and select Properties from the popup menu. In the Project Properties dialog box click on the category Libraries and click on the + sign next to Modulepath. Select Add Project then the org.astro Java Project and click on Add Project JAR Files. Keep in mind that if you try to add cyclic dependencies (e.g. from `org.astro` to com.greetings), NetBeans will display a dialog box with the message: Can’t add cyclic references (see Fig. 7).
  4. Modify Main.main() method like so and clean and build both modules.

Fig. 6 - Module Info commands
Fig. 6 – Module Info commands

Fig. 8 - Cyclic Dependencies error
Fig. 7 – Cyclic Dependencies error

com.greetings.Main.java

package com.greetings;
import org.astro.World;
/** @author javacodegeeks */
public class Main {
  /** @param args the command line arguments */
  public static void main(String[] args) {
    System.out.format("Greetings %s!%n", World.name());
  }
}

When you paste the new System.out.format(...) statement NetBeans recognizes the class World and proposes to add the import statement. This is only possible because you have already updated the module-info.java files of the two modules, which are shown below:

com.greetings.module-info.java

module org.astro {
    exports org.astro;
}

org.astro.module-info.java

module com.greetings {
    requires org.astro;
}

NetBeans 9 EA provides you with a visual representation of the dependencies (the module graph). Simply click on the Graph button while in the editor of org.greetings module-info.java to see a nice graph of the modules’ dependencies as shown in the following figure.

Fig. 8 - Module Graph
Fig. 8 – Module Graph

Run com.greetings module to see the output: Greetings world! as expected.

Compare to the original quick start guide of project Jigsaw to see how many typings you have saved yourself, thanks to NetBeans IDE.

4.3. Packaging modules

Packaging modules is very easy with NetBeans. Right-click on project com.greetings and select Properties from the popup menu. In the Project Properties dialog box (Fig. 9) click on the category Packaging under Build and select Create JLink distribution and Create Launcher and click OK. Next time you clean and build NetBeans will generate a Java runtime image inside dist folder of com.greetingsJava project which contains only the JDK modules needed for com.greetingsto run (i.e. only java.base module). No need to remember the syntax of jlink command.

Fig. 9 - Packaging Modules
Fig. 9 – Packaging Modules

4.4. Java modular project

As we mentioned in the beginning, NetBeans 9 only allows a single module per Java project. However, using a Java modular project one can define many modules inside this special Java project. We shall re-implement the previous modules using a Java modular project. This is an ant based project containing several modules and compiling them at once.

  1. File | New Project…
  2. Select Java (Category) and Java Modular Project (Project) (Fig. 10) and click on Next
  3. Enter ModularGreetings as the Project name and click Finish.
  4. Right-click on the newly created project and select New Module from the popup menu.
  5. Enter com.greetings as the module name and click Finish. NetBeans opens the module-info.java file for this module.
  6. Repeat previous step to create org.astro module.
  7. Create the dependencies between the two modules as described in 4.2
  8. Create (or copy from the previous modules created in 4.2) the packages inside classes of each module.
  9. Clean and Build. No need to add org.astro to the module path of com.greetings; it is done automatically.
  10. Run the project to see the correct output: Greetings world!

Fig. 10 - New Java Modular Project
Fig. 10 – New Java Modular Project

Fig. 11 - Java Modular Project
Fig. 11 – Java Modular Project

You see that the Java Modular Project has a number of advantages, like you don’t need to add explicitly other projects to the Module path; this is done automagically when you update the module-info.java.

4.5. Services

Loose-coupling refers to systems in which each component has, or makes use of, little or no knowledge of the definitions of other separate components. This allows the various components to change independently without affecting other components.

But let’s describe an example to understand what it’ all about before we dig into the details of the Quick Start Guide.

Suppose you have a Provider that provides a service. E.g. this could be an AlertService (which provides various system or application alerts), a CoordinatesProvider (which provides various coordinate systems, e.g. Lat/Lon, GEOREF, UTM etc.), an AlgorithmProvider (which provides various algorithmic solutions to a problem) etc. To achieve loose-coupling, you provide an interface to your caller classes hiding the actual implementation(s) behind it. The caller (or service consumer) classes do not need to know anything of the actual implementations; they only need to know how to access the relevant methods. Then, the implementations are somehow provided to the caller classes at runtime. This way the actual implementations can be changed at any time without the caller classes knowing about it, as long as the Provider interface does not change.

There are a number of ways to achieve loose-coupling. This is usually done by a service provider. The Service Locator design pattern provides a global point of access to a service without coupling callers (service consumers) to the concrete class(es) that implement(s) it. E.g. Spring uses Dependency Injection (one form of Inversion of Control), NetBeans’ RCP Modular API uses Lookups and ServiceProviders, etc. Jigsaw uses ServiceLoader from Java 6. The service consumer and service provider classes can reside in different modules.

From the Jigsaw Quick Start Guide, module com.socket provides a service NetworkSocketProvider for NetworkSockets. Two implementations of this service are provided in two different modules: org.fastsocket and org.smartsocket. Our service consumer module com.greetings needs a dependency only on the com.socket service provider module, but not on the service implementation modules.

Fig. 12 - Service Providers
Fig. 12 – Service Providers

Let’s see how can we achieve this. In NetBeans 9 EA, create a new Java Project com.socket as we saw before, without providing a Main class.

Create the two classes com.socket.NetworkSocket and com.socket.spi.NetworkSocketProvideras described in the Quick Start Guide, add a new module-info.java to transform the project to a Java 9 module and export these two packages to the consumer classes:

com.socket.module-info.java

module com.socket {
    exports com.socket;
    exports com.socket.spi;
    uses com.socket.spi.NetworkSocketProvider;
}

The last statement declares that this module provides the com.socket.spi.NetworkSocketProvider service to consumers. Don’t forget to clean and build this module for the changes to take effect.

Next, let’s create the org.fastsocket module. As before, create the two classes org.fastsocket.FastNetworkSocket and org.fastsocket.FastNetworkSocketProvider as described in the Quick Start Guide, add a new module-info.java to transform the project to a Java 9 module and add a dependency to com.socket module:

org.fastsocket.module-info.java

module org.fastsocket {
   requires com.socket;
   provides com.socket.spi.NetworkSocketProvider
        with org.fastsocket.FastNetworkSocketProvider;
}

The last statement provides an implementation of the com.socket.spi.NetworkSocketProvider service provider. Notice that this module does not export any packages!

However, the project contains compilation errors. Can you tell why?

We need to add com.socket to the modulepath (refer back earlier in this article if you don’t remember how to do that). Clean and build to make sure there are no errors.

You could repeat the above steps to create org.smartsocket in a similar way, however, the quick start guide doesn’t do that, so you could create your own implementations, if you wish, as an exercise.

Last, create the consumer com.greetings Java project (use another directory to not mess up with the com.greetings module we created in chapter 4.1 and 4.2) with a com.greetings.Main class, copy the contents from the Quick Start Guide, add a new module-info.java to transform the project to a Java 9 module and add a dependency to com.socket:

com.greetings.module-info.java

module com.greetings {
    requires com.socket;
}

Make sure you add in your modulepath com.socket and you are done. Clean and build, then run com.greetings and you will see an error:

Runtime exception

Exception in thread "main" java.lang.RuntimeException: No service providers found!
 at com.socket/com.socket.NetworkSocket.open(NetworkSocket.java:19)
 at com.greetings/com.greetings.Main.main(Main.java:8)

Why? We did exactly what the Quick Start Guide mentions. NetBeans Java projects require that you add org.fastsocket in the module path (which is then added automatically to the module-info.java) and then the exception goes away:

org.fastsocket.FastNetworkSocket

class org.fastsocket.FastNetworkSocket

As an exercise you may repeat the above using a Java Modular Project. And here you don’t need to add the service provider implementation modules (like org.fastsocket) to the module path of com.greetings.

But how does it work? Jigsaw uses ServiceLoader to locate the various service provider implementations:

ServiceLoader

ServiceLoader<NetworkSocketProvider> sl
           = ServiceLoader.load(NetworkSocketProvider.class);
Iterator<NetworkSocketProvider> iter = sl.iterator();

sl.iterator() will iterate through org.fastsocket.FastNetworkSocketProvider and com.smartsocket.SmartNetworkSocketProvider (if you implemented it).

Under the hood, ServiceLoader creates a provider configuration file, which is stored in the META-INF/services directory of the service provider’s JAR file. The name of the configuration file is the fully qualified class name of the service provider, in which each component of the name is separated by a period (.), and nested classes are separated by a dollar sign ($). In other words, ServiceLoader creates a text file package.Provider inside build/classes/META-INF/services/ folder (or dist/provider.jar) of the module which contains the fully qualified names of the implementation classes, e.g. package.ProviderImpl.

In our example, com.socket/build/classes/META-INF/services/contains the text file com.socket.spi.NetworkSocketProvider which contains the fully qualified names of the implementation classes org.fastsocket.FastNetworkSocketProvider (and com.smartsocket.SmartNetworkSocketProvider if you implemented it).

Or at least, this should have been the ‘under the hood’ implementation if Java 6 ServiceLoader was used! Unfortunately, Java 9 has modified ServiceLoader‘s implementation.

Java 6 ServiceLoader has a number of restrictions:

  • it isn’t dynamic (you cannot install/uninstall a plugin/service at runtime)
  • it does all service loading at startup (as a result it requires longer startup time and more memory usage)
  • it cannot be configured; there is a standard constructor and it doesn’t support factory methods
  • it doesn’t allow for ranking/ordering, i.e. we cannot choose which service to load first (ordering of services is done as they are discovered)

Additionally, Java 9 has modified Java 6 ServiceLoader as follows:

  • No relative services; the new module-based service locator does not have relative behaviour
  • Ordering of services (as they were discovered) is lost
  • All service interfaces and implementations on the module path are flattened into a single, global namespace
  • No extensibility/customizability of service loading; the service layer provider must provide a fixed mapping of available services up front
  • multiple-site declarations; every module that uses a service must also declare that the service is being used in the module descriptor; no global layer-wide service registry

In other words, if you search for com.socket/build/classes/META-INF/services/ or com.socket/dist/com.socket.jar/META-INF/services/ you will find nothing.

NetBeans RCP provides ServiceProvider instead, which does not have the drawbacks of ServiceLoader mentioned above. It is dynamic, so you can plugin/unplug modules while your application is running, it doesn’t load all services at startup and allows you to set priorities (with the position attribute of @ServiceProvider annotation). Unfortunately, it does not work with Jigsaw.

5. NetBeans further improvements

To make a package available to other modules, one must edit module-info.java and add an exports statement passing the package name as an argument. This results in the package icon to change to an icon with an open lock instead of a locked one.

A nice shortcut would be to be able to right-click on a package and select an action Export Package as shown in the following figure, that would automatically modify the module-info.java accordingly without the need to type the export command.

Fig. 13 - NetBeans improvement; add an Export Package menu item
Fig. 13 – NetBeans improvement; add an Export Package menu item

This functionality already exists in NetBeans RCP Modules API, the API that comes with NetBeans Rich Client Platform.

In a project that consists of many modules, it is often difficult to find inside which module our dependencies exist. A nice addition would be to be able to search the class we look for throughout our modules (and/or libraries modules).  A similar functionality already exists in NetBeans RCP Modules API, the API that comes with NetBeans Rich Client Platform.

Clicking on a hints blob, when an error is encountered because NetBeans cannot find the dependency, would open a dialog box where the developer can type the desired class, as shown in the following figure, and then choose the appropriate module. The same dialog box could be accessed when the developer requests to add a module dependency to the module path (e.g. by right-clicking on Libraries).

Fig. 14 - NetBeans improvement; search for modules' dependencies
Fig. 14 – NetBeans improvement; search for modules’ dependencies

Finally, one can add a new module to a Java Modular Project, but there is no way to remove a module, while these lines are written at least. This is an EA bug that will be fixed.

NetBeans 9 EA is still in heavy development and it hasn’t passed official testing (a.k.a. NetCat), yet, so it is normal that some bugs or strange behaviour will be encountered at this stage.

6. Conclusion

In this article we saw how NetBeans 9 EA supports JDK 9 EA and makes the life of the developer easier. As a developer you don’t need to remember the details of how to build and execute java modules using the module path, or how to compose the jlink command; NetBeans 9 hides the details. JShell is also well integrated. Some improvements can, of course, make the life of developers even easier, but these will come in future releases of NetBeans or as plugins.

We saw the two kinds of projects one can use to create modular Java applications. We saw the use of four of the five available commands one can use inside module-info.java: exports, requires, uses and provides. opens allows other modules to use reflection to access types in the package that you open.

Specific packages in normal modules can be ‘opened’, so that only that package is available for deep reflection at run time:

com.greetings.module-info.java

module com.greetings {
    opens com.greetings;
}

Several Java frameworks and tools rely heavily on reflection to access your non-exported module’s code at runtime. They provide features such as dependency injection, serialization, implementation for the Java Persistence API, code-automation, debugging etc. Examples are Spring  and Hibernate. These frameworks and libraries do not know about your application modules but they need access to the types and private members of your modules, which breaks the premise of strong encapsulation in JDK 9. One can also open the whole module for reflection, e.g.:

com.greetings.module-info.java

open module com.greetings {
    requires com.socket;
}

Comparing exports with opens, the exports statement lets you access only the public API of the specified package at compile-time and runtime, while the opens statement lets you access public and private members of all types in the specified package using reflection at runtime.

It is not all roses when working with Jigsaw. The Community of Experts hasn’t embraced Jigsaw and have many concerns about a number of critical deficiencies that they have encountered.

7. References

  1. NetBeans 9 EA
  2. NetBeans 9 EA JDK 9 Support
  3. Ultimate Guide to Java 9, Sitepoint
  4. JDK 9 Feature Complete, JavaCodeGeeks
  5. Java Magazine, July-August 2017
  6. Java 9 series: JShell, Voxxed
  7. Java 9 series: HTTP/2 Client, Voxxed
  8. Java 9 series: the JVM, Voxxed
  9. Java 9 series: HTML5 and Javadoc, Voxxed
  10. Java 9 series: Concurrency Updates, Voxxed
  11. Java 9 series: Variable Handles, Voxxed
  12. Java 9 series: Encapsulate Most Internal APIs, Voxxed
  13. Java 9 series: Multi-Release JAR Files, Voxxed
  14. Java 9 series: Segmented Code Cache, Voxxed
  15. Java 9 series: Convenience Factory Methods for Collections, Voxxed
  16. Critical Deficiencies in Jigsaw
  17. Bateman A. (2016), “Prepare for JDK 9”, JavaOne.
  18. Bateman A. (2016), “Introduction to Modular Development”,JavaOne.
  19. Bateman A. & Buckley A. (2016), “Advanced Modular Development”, JavaOne.
  20. Buckley A. (2016), “Modules and Services”, JavaOne
  21. Buckley A. (2016), “Project Jigsaw: Under The Hood”, JavaOne.
  22. Bateman A., Chung M., Reinhold M. (2016), “Project Jigsaw Hack Session”, JavaOne.
  23. Deitel P. & Deitel H. (2017), Java 9 for Programmers, 4th Ed., Deitel.
  24. Evans, B. (2016), “An Early Look at Java 9 Modules”, Java Magazine, Issue 26, January-February, pp.59-64.
  25. Gupta A. (2015), JDK 9 REPL: Getting Started, JavaCodeGeeks
  26. Jog T-M. (2016), Learning Modular Java Programming, Packt.
  27. Mak S. & Bakker P. (2016), Java 9 Modularity, O’Reilly (Early Release)
  28. Reinhold M. (2016), Problem modules reflective access, Voxxed
  29. Sharan K. (2017), Java 9 Revealed For Early Adoption and Migration, Apress.
  30. Verhas P. (2017), Java 9 Programming By Example, Packt.
  31. Zhitnitsky A. (2015), Java 9 Early Access: A Hands-on Session with JShell – The Java REPL, JavaCodeGeeks.

Ioannis Kostaras

Software architect awarded the 2012 Duke's Choice Community Choice Award and co-organizing the hottest Java conference on earth, JCrete.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button