Platinum Partner
java,swing,oracle

Java+Swing in 2013. Is it worth it?

Java Swing was created quite a while ago. If you try searching by the keywords "Swing”, you won't get many articles. But, you'll discover that some people really hate Swing:

  • "10 Things I Never Want to See a Java Developer Do Again... 2. Write/debug or even use a Swing application"
  • "Unlike Swing ... which was ugly by itself"

In my opinion, that’s unfair. I am not saying that Swing is perfect. That’s certainly not true. But I’ll try to describe present Swing’s strong and weak points that I have encountered in my practice.

Why using Swing?

I’ve been using Swing for a couple of years, mainly in the evenings. I am developing Visual Watermark, an application for protecting multiple photos from copyright infringement. In 2011, I made a Java version. I wanted to port my application for the Mac and polish the user interface, but developing a separate application for each platform was out of the question. In early 2011, the following UI libraries were available for cross-platform development:

  • QML was totally bug ridden: The menu appeared below the components, the demo version constantly crashed, and QML was not supported by QtCreator. Accelerated image rendering was implemented only last fall, in Qt5.
  • Qt didn’t suit my needs because QML was half-baked at that time, so I would have to write some code in C++. Surely C++ is a magnificent language used by many smart people. However, taking into account my lack of experience and the complexity of the task at hand, using C++ would be an overkill. Moreover, I found out that Xcode couldn’t refactor C++ code.
  • Juce’s functional capabilities suited my needs just fine. It wasn’t glitchy and didn’t crash. It was affordable, and open source too! Juce deprecated the use of the new/delete operators, allowing the programmer only to create objects on the stack. In theory, that was supposed to reduce the number of pointer-related bugs. I don’t know if that is so, because I stopped short of coding in C++.
  • In Adobe Air, the support for multi-threading is too complicated. You have to create a separate SWF project for each thread, and it’s difficult to exchange data between threads. In the simplest integer calculation performance test, ActionScript falls far behind Java, C#, and C++ implementations.
  • Seemingly, the Mono+GTK combination could have solved my problems. But at that time, I decided not to use it because of the obvious bug: Hot keys didn’t work in GTK when a non-English layout was used. Judging by MonoDevelop, that bug has not been fixed yet. Possibly there are other major shortcomings, too.
  • JavaFX was not available for the Mac.
  • SWT is much easier to use than Swing. All in all, SWT is pretty good. I didn’t use that library simply because it was the last one that I considered using. I had already spent a lot of time, so I just stopped my experiments as soon as I found a bug (buttons slightly “floated” up and down on the tool bar).

At that time, Java was part of Mac OS X and had an excellent native look & feel. And the size of the JRE distribution package for Windows was only 12 megabytes. I was under the illusion that I would surely succeed. As a result, after two or three months of work, I had the first version of my application based on Java Swing. 

The bugs that I mentioned have already been fixed in QML and JavaFX. So if you are ready to work with a scenic graph, you might want to try them out.Qt is now managed by Digia, which released a beta version for iPhone and Android. So hopefully the library has a future.

JavaFX was open sourced this February. In JDK 9, it is expected to become compatible with OpenJDK. It’s hard to say yet when JDK 9 will be released. The release of JDK 8 is planned for early 2014.

Good things

I’ll start with some good things, so don’t think I’m just another Swing hater. 

All rendering is hardware accelerated. Each Swing application is rendered on the GPU, without any efforts by the programmer. So you can use animations in your application, even if it’s going to run in full screen mode or be maximized on a Full HD screen. 

MVC. Swing is criticized for being “heavy”: Any component consists of a representation, controller, and model. However, this approach lets you easily add a new feature to an existing component. Everything is very flexible. 

Java is managed code. You can get rid of tons of bugs “available exlusively” for C++ developers. The risk of Access Violation is minimized. But it doesn’t mean there won’t be other bugs (such as memory leaks) in your program. 

It’s an excellent development environment. Use Eclipse, Intellij IDEA, or NetBeans — whatever better suits your needs. Each of these IDEs offers you refactoring, code formatting, autocomplete, support for unit tests, and tons of libraries. Layout managers, support for native objects, strings, and the Web — you name it. All these things make Java a great platform. 

A lot of answers to questions. For example, take a look at StackOverflow questions on each UI library. One in a hundred questions of so is about Swing. In practice, it means that most of the problems have already been solved. Most likely you won’t have to fight a problem on your own.

Bad things

The previous part sounded like a sweet press release, but I can fix that. :-) Here’s what you may encounter in practice. 

Critical bugs are not fixed. File.exists doesn’t work from the moment when JDK7 was released, and there is no fix yet. Even if a bug is critical, you may have to wait for a fix literally for years. The situation may become even worse if you are going to use native code. I found out the hard way that using modal windows (e.g. opening an OpenFileDialog) can freeze my application on some computers. That happens even if you use Java Native Foundation just like shown in the examples in its documentation. Offering a bounty for solving my problem on StackOverflow didn’t help me either. :-) Actually, you can evade the File.exists bug by using java.nio classes. Java.nio is a new API, whose purpose initially was to solve performance problems when handling huge folders. What you should do:

  1. Launch the application with the parameter
    –Dfile.encoding=UTF-8
  2. Instead of File.exists, use
    Files.exists(Paths.get(fileName))
  3. Instead of File.listFiles, use 
    try (DirectoryStream ds = Files.newDirectoryStream(folder)) {
            for (Path file : ds) {
            // do something
            }
    } catch (IOException e) {
            e.printStackTrace();
    }

Or you can try fixing the bug, and then pushing your fix through a series of reviews. 

Swing is hardware accelerated only (Mac-only!). It means that your application won’t run in VMware or Parallels, and a remote desktop won’t do either. If these limitations are not acceptable, SWT might be your choice. 

There are no 32-bit builds for the Mac. The official build is 64 bit only. Alas, I don’t know what’s the reason for that choice. I can only guess that some bugs may be the case. Henri Gomez used to support 32-bit and universal builds for some time. You could download ready-made builds from his page on code.google.com. Unfortunately, the lack of time and new job forced Henri to stop supporting his project. He said goodbye to everybody and uploaded his build scripts to GitHub: https://github.com/hgomez/obuildfactory You can use them to make OpenJDK for the Mac or Linux. That’s good but not awesome. You cannot use the scripts to make a 32-bit build for the Mac. The JDK contains tons of configuration files that only let you make a 64-bit build for the Mac. If you change a key in the main file, the build won’t work at all. I don’t know how Henri Gomez managed to make 32-bit builds. 

Include JRE in your distribution package. The Oracle management thinks that a standalone self-contained installer with a bundled JRE for the target platform is a better model for application distribution. The most probable reason for that approach is that Java applets contain tons of vulnerabilities. Flash used to be the bad boy, but now Java has taken its place. Apple seems to be the strongest supporter of Oracle’s approach. It even removed Java in Mac OS 10.7 Lion. Apple also forcibly switches off Java when installing system updates. The size of JRE 7 is some 100 megabytes, or about 50 megabytes when archived. Unfortunately, the size of JRE grows with each update, and that’s not good. 

Not all BufferedImage objects use hardware acceleration. Hardware acceleration is only used for BufferedImage.TYPE_INT_*. So starting with JDK7, it is not worthwhile to use TYPE_4BYTE* or TYPE_3BYTE. 

When BufferedImage raster data is accessed, the image is no longer rendered on the GPU. The reason for that approach is clear: If the user modifies the data, you don’t know when to reupload it to the video memory as there is no “modification is over” method. At least, that’s logical. When developing Visual Watermark, I used a C++ library for loading images, and I needed to convert the resulting pixels into a BufferedImage object. Working pixel by pixel was too slow, so I had to write the image directly to the raster buffer. As soon as I called getData() for the raster, hardware acceleration stopped working with all my pictures. I looked into the DataBufferInt code and found a solution for this problem, using reflection. Here’s my little helper class:

import java.awt.*;
import java.awt.image.*;
import java.lang.reflect.Field;

import sun.awt.image.SunWritableRaster;
import sun.java2d.StateTrackableDelegate;

// Standard library prevents image acceleration once getData() method is called
// This class provides a workaround to modify data quickly and still get hw-accel graphics
public class AcceleratedImage {
    // Returns data object not preventing hardware image acceleration
    public static int[] getDataBuffer(DataBufferInt dataBuffer) {
        try {
            Field field = DataBufferInt.class.getDeclaredField("data");
            field.setAccessible(true);
            int[] data = (int[])field.get(dataBuffer);
            return data;
        } catch (Exception e) {
            return null;
        }
    }

    // Marks the buffer dirty. You should call this method after changing the data buffer
    public static void markDirty(DataBufferInt dataBuffer) {
        try {
            Field field = DataBuffer.class.getDeclaredField("theTrackable");
            field.setAccessible(true);
            StateTrackableDelegate theTrackable = (StateTrackableDelegate)field.get(dataBuffer);
            theTrackable.markDirty();
        } catch (Exception e) {
        }
    }

    // Checks whether current image is in acceleratable state
    public static boolean isAcceleratableImage(BufferedImage img) {
        try {
            Field field = DataBuffer.class.getDeclaredField("theTrackable");
            field.setAccessible(true);
            StateTrackableDelegate trackable = (StateTrackableDelegate)field.get(img.getRaster().getDataBuffer());
            if (trackable.getState() == sun.java2d.StateTrackable.State.UNTRACKABLE)
            return false;
            field = SunWritableRaster.class.getDeclaredField("theTrackable");
            field.setAccessible(true);
            trackable = (StateTrackableDelegate)field.get(img.getRaster());
            return trackable.getState() != sun.java2d.StateTrackable.State.UNTRACKABLE;
        } catch(Exception e) {
            return false;
        }
    }

    public static BufferedImage convertToAcceleratedImage(Graphics _g, BufferedImage img) {
        if(!(_g instanceof Graphics2D))
            return img;// We cannot obtain required information from Graphics object
        Graphics2D g = (Graphics2D)_g;
        GraphicsConfiguration gc = g.getDeviceConfiguration();
        if (img.getColorModel().equals(gc.getColorModel()) && isAcceleratableImage(img))
            return img;
        BufferedImage tmp = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency());
        Graphics2D tmpGraphics = tmp.createGraphics();
        tmpGraphics.drawImage(img, 0, 0, null);
        tmpGraphics.dispose();
        img.flush();
        return tmp;
    }
}

Use my helper class as follows:

DataBufferInt dataBuffer = (DataBufferInt)bufferedImage.getRaster().getDataBuffer();int[] data = AcceleratedImage.getDataBuffer(dataBuffer);// Modifying the dataAcceleratedImage.markDirty(dataBuffer);

I didn’t test the above code on any displayed images.

There is no built-in support for animation or semi-transparency. The javax.swing.Timer object is good for two things:

  1. You can use it to add animation to your components.
  2. The class is very simple, so doing that takes a lot of time.

The Timing Framework library offers an easier way to create animations. You can create an animation as follows:

Animator viewAnimator = new Animator.Builder() .setDuration(duration, TimeUnit.MILLISECONDS) .setStartDirection(Direction.FORWARD) .setInterpolator(new AccelerationInterpolator(0.3, 0.7)) .setRepeatCount(1).addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { // Changing state here repaint(); } @Override public void end(Animator source) { // Do something when animation completes } }).build();viewAnimator.start();

Most often position or semi-transparency animation is used. Swing lets you control the position just fine, but its standard components do not support semi-transparency. The problem is not limitations of the graphic engine but a lack of the getAlpha/setAlpha property in components.

Java applications won’t run in Mountain Lion because of GateKeeper. To solve the problem, you have to sign up for the Mac Developer Program, which will cost you $99 a year. Apple will give you a certificate for signing your code, and the problem will be over. You can sign your application bundle as follows: codesign –s "Developer ID" –f "path-to-my-app.app"

Bottom line

In my opinion, Swing’s major drawback is that you can never be sure about its future because of some critical bugs. Only browser plugin vulnerabilities are fixed from time to time. It almost looks like the library is no longer supported. All other problems seem insignificant in comparison. 

It makes me sad to think that Swing may be gone any time soon. You see, Swing lets you develop desktop applications quickly and easily, and there are tons of ready-made, free code that you can reuse. 

I still hope that Oracle developers will find some time and fix the problems in their platform.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}