Chapter 4. Glass Breaking

I don’t know for sure when I decided that the kayakers behind us were in trouble. Our minds were occupied by the chaos around us, and the situation kind of snuck up on us. Barton Creek was in flood, and it was pounding furiously. We’d left with 10 paddlers, but needed to keep a safe distance. We divided into groups of three so that each group could keep an eye on the others. The day had already started badly; a fireman in an unrelated party had died on this same stretch of creek. An expert boater had been foolishly paddling alone. Now, we had problems of our own.

After we’d paddled for about an hour, we pulled over into a huge eddy to get the group together and plan our assault on the next dangerous stretch of river. In truth, the banks were very dangerous, with trees that could trap you like a kitchen strainer while the water piled up and poured over you, but the main lines were pretty straightforward. I’d flipped once, but rolled back up easily. But we’d passed a few places that could have given you trouble, had you been unlucky enough to blunder into them, or too cocky to skirt the danger. The last party of three was missing, and we had no way of getting back up the river. We waited for two hours, but the last group of three failed to join us. We waited until there was little daylight left, and then we headed down the river. Eventually, we discovered that one in the trailing party had tried to punch a hole that none of us was brave or stupid enough to run, and had to be rescued by helicopter. I’ve never run Barton Creek again with water that high.

I’ve developed a good instinct for trouble on the river, and at work. In this profession, I generally know when a technology smells wrong, or dangerous, and I guide my customers away. I’m sensing that danger around Java right now. It’s getting too difficult to manage, and both evolutionary and revolutionary steps to remedy the problem are failing us. In this chapter, I’ll introduce some of the basic problems.

Java’s New Job Description

So far, I’ve tried to make the case that Java’s always been a generalized programming language, with the syntax and core community coming from the C++ systems language. Also, I’ve suggested that most early Java applications focused on the user interface. You could download Java and get something running very quickly.

Once Java moved to the server side, it became the core server-side development language. Java carries an increasing load in enterprise development, from object-relational mapping with distributed transactions to messaging with XML binding for service-oriented architectures. So the job that we use Java to do is ever changing. The language is remarkably flexible, so it’s lived up to the challenge so far.

But all of the extra power comes with added complexity. Where does that leave people who need to learn a language quickly, or the Java programmer who wants to solve a simple problem, or companies like start-ups that value productivity over all else? As competitive pressures force us to meet shorter and shorter schedules, a generalized Java is just not enough anymore. At some point, Java will prove inadequate. Let’s look in detail at what we’re asking Java to do.

Typical Requirements

If Java dies, I think it will be replaced one niche at a time. Java’s popular in several niches. It’s floundering in some and thriving in others:

  • Java’s become indispensable for writing middleware , the systems software that fits between an application and an operating system. Java’s many libraries, performance, portability, and ubiquity make it a good fit for middleware, and that’s likely to continue.

  • For servlets and web programming in general, Java needs a faster feedback cycle, and needs to get better at managing strings. PHP is far more productive for this environment. Java’s not the only reason: web programming is a mess for many reasons. But Java just isn’t very good for the simplest and most typical applications.

  • For XML processing, better alternatives exist. I’d argue that Java’s over-reliance on XML is part of the problem, but let me point out that Java is not a particularly good language at handling XML either. XML requires excellent string parsing and manipulation, and Java is just too verbose in this space. Already, the Ruby XML processing libraries, for example, are friendlier than the Java versions, and nearly as fast, for most jobs. Some other languages have excellent XML support. They will only get better over time.

  • For large enterprise projects requiring things like distributed transactions across multiple resources, heavy legacy integration, and code that relies on niche libraries , Java’s large libraries and the availability of Java developers make it a natural fit. It will continue to find a role here for quite some time. Be careful, though. Most projects in the enterprise are smaller projects that could benefit from a more productive language.

Instead of looking at the entire Java landscape, let’s narrow it down a bit and consider the requirements for the most typical Java job. I’ll go out on a limb and suggest that the most common Java job is to take a big, fat relational database and baby-sit it with a web-based user interface. As a consultant, I see variations of this job more often than any other.

I realize that I’m painting Java into a smaller niche than it’s currently occupying. I do think there’s cause to do so. From the beginning, Java has been a converted systems language. The impressive list of libraries expands that scope, and the broad and deep pool of programmers makes it compelling for large enterprise applications. But Java never really has been a general-purpose applications language, though that’s the place that most of us use it today.

The Learning Curve

If you’re concentrating on putting a web-based frontend on a relational database, Java framework designers have solved this problem repeatedly for eight years. I’ve got to admit, Java hasn’t gotten much better at this job since the invention of JSP. Take a look at one of the earliest servlet APIs in action:

    public class HiMom extends HttpServlet {
      public void doGet(HttpServletRequest req, HttpServletResponse res)
          throws ServletException, IOException {
        response.getWriter().println("<HTML>\nHi, Mom\n</HTML>");
      }
    }

True, this programming style leads to ugly code with nearly impossible maintenance. It couples view logic much too tightly to business logic. But it is very easy to understand. With the first release of Tomcat, after a few minutes of setup and less than 10 lines of code, you could write a “Hello, World” servlet.

Now the same application involves more effort. With the latest release of Tomcat, you can’t just write a servlet anymore. You also need to code up a deployment descriptor and package it all up in a standard WAR file. That means you’ve got to learn more about Tomcat , more about the servlet specification, and more about XML. As a consequence, the getting-started documentation for Tomcat has grown from a couple of pages to dozens of pages.

You might not think that substantial increases in the learning curve for Tomcat matter much. You might be willing to make such an investment in Tomcat, because it’s such a core technology. The problem is that it doesn’t stop with the servlet API. You need much more to build a typical Java application today than you needed five years ago:

  • You’ll likely need to understand Ant , the typical tool that most of us use to build and deploy web applications.

  • Then, you’ll need to understand Tapestry , or Struts , or some other web MVC framework, to help you organize your user interface code base.

  • Most of us try also to learn an object relational mapper, like Hibernate . While it does relieve some of your persistence burdens, it also imposes a steep learning curve.

  • You’ll probably want a framework like Spring to organize your application resources and make this whole strategy testable.

  • You’ll need some education on how to use these tools to integrate them and use them together effectively.

My clients that move to Java from another language just shudder when they see my recommendation of five weeks of education, which lets them cover only the fundamentals. Java is no longer an approachable language for them.

Java for the typical application

True, Java has improved some aspects of this problem. If you’ve got a highly normalized relational database that doesn’t lend itself to an object model very well, you can map it better today than you could then, because of the emergence of object relational mappers like Hibernate and JDO . You can better separate the business logic from the view logic, with Struts and better emerging alternatives like Tapestry. You can attach services like security and distributed transactions to any Java object with frameworks like Spring.

But if you really come back to the core problem, a web-based user interface on a relational database, you have to learn much more to do the job today than you had to learn five years ago. And you have to work harder to achieve the same results. Most of the added value deals with corner cases, or noncentral problems. When all is said and done, these advanced frameworks will drive the Java language away from the base that made it so popular. When that happens, Java will be a niche language for large-scale enterprise development.

Agile Processes

While the requirements for the typical Java application have remained relatively static, radical changes are transforming the typical process that you might use to build it. While not many Java programmers would say they use agile methods like SCRUM or Extreme Programming , more and more of them are using the core techniques from agile processes :

Simplicity

Agile methods suggest that you should use the simplest thing that will work. Simpler frameworks like Spring now displace complex frameworks like EJBs with increasing regularity.

Automated unit testing

We are in the midst of a testing renaissance, and the JUnit framework and agile processes light the way. At conferences I attend, classes like test-first development garner ever-increasing attendance, and polls to the audience indicate that testing is much more common than it has been.

Shortened iterations

Shorter schedules and the need for better integration of customer feedback shorten product development cycles, and also the smaller iterations within those cycles.

Development processes and Java

Java’s community and tools provide excellent support for agile development, but there’s a catch. Java is not such a good language for agile development. Java is not the simplest of languages. Nor is it friendly to very short iterations. If these two ideas are not clear to you now, they will be clear by the time you finish this book. Other languages let you move from one change to the next without a cumbersome compile/deploy cycle. Other languages have a more expressive syntax, and other frameworks take you to a higher, more productive level of abstraction.

Even as we begin to understand that Java is not the most agile language, those using other dynamic languages are using agile techniques like automated testing to shield them from the problems related to programmer-friendly type and exception strategies. Java’s founders believed that it’s always better to catch potential bugs at compile time. They did not consider that features like static typing and a heavy emphasis on checked exceptions come at a cost.

If we were to choose a language based on the development methods that we value today, Java would almost certainly not be our language of choice. As the principles promoted by agile developers become prominent, the Java language will experience increasing pressure.

Basic Java Limitations

I’ve painted a picture of the average project. The average team builds or ports applications that will deliver a web-based frontend on a relational database, potentially with other less meaningful services. The team probably uses increasingly agile principles, and likely wants to do unit testing. The team typically works under short schedules and great pressures. And given more dynamic alternatives, Java is not at all the language that I’d usually choose for such a project, in such an environment:

  • The many frameworks designed to simplify the Java development experience do make experienced Java developers more productive, but make the learning curve too steep for those new to Java.

  • Compile-time checking of exception and types adds safety, but comes at a cost of additional time and syntax.

  • Java’s inability to express structured data leads to an over-reliance on XML, with the corresponding additional complexity and bloat.

  • Java’s many compromises, like primitives, make Java harder to learn and more complex to write.

  • Java is more dynamic than C++, but is nowhere near as dynamic as languages like Smalltalk and Ruby. Java developers are finding metaprogramming, but they’re not able to execute on those ideas fast enough.

  • Java’s long compile/deploy cycle is much longer than interpreted, dynamic alternatives.

Taken alone, none of these issues hurts enough to matter. Taken together, Java becomes much less productive for most developers.

Typing

One of the most fiercely debated topics in programming languages is the benefit of strong, static typing strategies. Java’s strategy opts for as much compile-time checking as possible. Let’s take a quick overview of programming language design, in layman’s terms. Then, you can put Java into context. When building a language, a designer needs to answer two typing questions relatively early in the design process.

Strong Versus Weak Typing

Strong versus weak typing decides how a type is enforced, or interpreted. In a weakly typed language (like C), variables can be coerced easily, or interpreted as something else. A strongly typed language strictly enforces compatible types across operations. It probably doesn’t surprise you that Java is a strongly typed language.

Ruby, Smalltalk, and Python also enforce strong typing, which might surprise you. Many developers believe Smalltalk, Python, and Ruby are so productive because they are weakly typed. They are misinformed. Consider this brief Ruby example:

    irb(main):003:0> i=1
    => 1
    irb(main):004:0> puts "Value of i:" + i
    TypeError: cannot convert Fixnum into String
            from (irb):4:in '+'
            from (irb):4

In the first line, the undeclared variable i takes on the value of 1. At this time, Ruby decides that i is a Fixnum. When Ruby interprets the third line, it sees the + operator after the string, and tries to concatenate i. Of course, Ruby doesn’t know how to concatenate an integer to a string, so it throws an error. That’s clearly an example of strong typing. (Actually, I’ve oversimplified things a little. You can dynamically change the definition of Ruby classes and objects at runtime, and this weakens the typing somewhat. Still, on a continuum from strong to weak typing, Ruby would lean slightly to the strong side.)

In a similar situation, a language with weaker typing may instead coerce types to a compatible form, as in C. Consider this example:

    int a = 5;
    float b = a;

In the second line, C coerces the value of the integer to float. Other examples are even worse. In C++, the () cast operator does not yield type safety, so you could say, for example:

    Cat *cat;
    Dog *dog = (Dog *)cat;

These are legal C++ statements. Instead of reporting an error, C++ will happily go on stomping through memory. Languages with very weak typing simply do not capture typing errors, so the behavior of certain operations is undefined. Weaker typing is sometimes convenient, and less predictable. As you’ve seen, typing is not always black and white. It’s also a highly contentious issue among language experts. Strong versus weak typing is on a continuum. Some strongly typed languages like Java allow loopholes by letting the user cast objects to another type. Languages with the strongest possible typing allow no loopholes. Weaker typing allows, and may even require, coercions. The weakest possible typing doesn’t do type checking at all at compile time or runtime, like Assembly language, for example.

Static Versus Dynamic Typing

The more interesting question by far is when typing is enforced. Static typing binds a type to an object, and language constructs like variables and parameters. Dynamic typing binds a type to an object at runtime. Dynamic typing doesn’t say anything about a variable’s container, or anything that a variable passes through. The type is bound to the object. Therefore, the type of containers can change. An imperfect rule of thumb is that static languages force you to declare variables, but dynamic languages don’t.

Ironically, most dynamic languages also tend to be strongly typed. Most weakly typed languages tend to be static. Said another way, strong typing can be dynamic or weak, but weak typing is usually also static. You don’t find many weakly and dynamically typed languages, beyond Assembly language. Figure 4-1 places programming languages on two axes. Java has strong, static typing. You know this, because you get type mismatch errors when you make certain kinds of mistakes. Compiling this:

    class TypeTest {
      public static void main(String args[  ]) {

        i = 4;                // Nope!!! Static typing

        int j;
        j = 4.2;              // Nyet!!! Strong typing
      }
    }

...gives you this result:

    TypeTest.java:3: cannot resolve symbol
    symbol  : variable i
    location: class TypeTest
        i = 4;

    TypeTest.java:5: possible loss of precision
    found   : double
    required: int
        j = 4.2;
Java is a strongly and statically typed language
Figure 4-1. Java is a strongly and statically typed language

Sometimes, that’s good. After all, a bug that gets caught at compile time takes much less time than a bug that gets solved much later. In general, though, the dynamic programmers that I interviewed said static typing simply mauls productivity.

Syntax

Initially, you immediately can see that Java’s syntax forces you to do more work. You have to declare and type all of your variables and parameters. You also need to cast objects that are compatible but different, and convert objects that aren’t. The extra syntax provides value—the compiler has more information to catch bugs earlier. There’s a cost, too. Static typing makes you work harder to enter equivalent code to dynamically typed languages, but you also have more lines of code to understand, maintain, or enhance. It’s very difficult to prove or disprove the notion that static typing makes you more or less productive in terms of hours at the keyboard, but you can show that static typing leads to more characters, and more code to read and maintain.

Raw code count is not definitive; if it were, Perl, with all of the two- or three-character shortcuts, would be the most productive language of all time. Still, it’s suggestive. Java’s syntax wouldn’t be such a problem if you could limit the extra code to a few lines of code at the top or bottom of a program, but you can’t. You need to declare types for parameters. You need a cast every time you take something out of a collection. This syntax only gets more invasive with generics.

Thought Process

Some of the costs related to typing are hidden costs . I believe that one such cost is related to high-level, conceptual work versus finishing work. It’s usually preferable to do conceptual work first and finishing work later, because much of your code will be thrown away, especially at early stages. As your program takes shape, you can do more and more detailed work. You make the expected case work, and then you work through noncritical path issues.

Java forces you to work the opposite way: to make things compile, you must deal comprehensively with typing, which involves dominantly finishing issues. Also, many of the compiler errors in Java might not even be problems at all in a dynamically typed language.

Code/Compile Cycle

Dynamic typing comes into play especially when you need to experiment. Remember, you must declare variables in statically typed languages. In Java, that means you need to start each application with a class definition, and it snowballs. You can’t just jump in and evaluate a single line of code—the compiler just doesn’t have enough information. Instead of just simply evaluating statements, you need to blow out a class, type everything, compile, and execute. In Smalltalk, Lisp, Basic, and Ruby, you can just start typing. For simple experimentation on an initial cut at a Fibonacci sequence, here’s the Java version:

    class Fib {
      public static void main (String args[  ]) {
        int x1 = 0;
        int x2 = 1;
        int total = 1;
        for (int i=0; i<10; i++) {
          System.out.println(total);
          total = x1+x2;
          x1 = x2;
          x2 = total;
        }
      }
    }

It’s 13 lines; 41 words; 226 characters. Keep in mind that Java forces you to declare the class to explore, and that’s what we’re measuring here. On the command line, you need to save, compile, and run. The Ruby counterpart looks like this:

    x1 = 0
    x2 = 1
    100.times do
      puts x2
      x1, x2 = x2, x1+x2
    end

It’s 6 lines; 16 words; 57 characters. Notice how the code just flows better. Read it in English. But the biggest impact is on experimentation. You just type and go. You can cut and paste right on the console screen. You’ll use command retrieval to repeat the lines that you need. And these advantages come into play in IDEs as well. Further, if you need 100 iterations, the Java version breaks because an int is not big enough. Ruby still works fine.

This is a trivial example, and probably not completely fair. After all, the Java version packages up a full class and the Ruby version doesn’t need to. But you’ll find that as we go on, the examples get more and more compelling, especially for the dynamic, reflective style of programming that leading Java developers seek today.

As you add the Web and other deployment steps, the case for dynamic languages gets more compelling, because you can make changes and immediately see the results, instead of having to compile and deploy, and maybe even bounce your servlet engine. Web-based programming gets very easy. Just make a change, and hit Reload.

From my small forays into Basic (where I made my spending money in high school by writing games) and Smalltalk (where I did marketing demos), I miss the rapid feedback cycle afforded by dynamic typing and an interpreter the most.

Adaptability

If you’ve been coding in Java for most of your career, you probably don’t know that you have to jump through so many hoops just to support static typing, but you do. One of the greatest typing costs comes into play when you refactor. Think of the impact of a simple type change for a member variable. You’ve got to change the property, the getters and setters, every type cast, and every parameter usage. Dynamically typed languages delay the binding of a type to a variable or a parameter, so you often don’t need to make any change at all to support a simple type change. For Smalltalk, for example, you can change a type easily. If the new type supports all of the messages of the old type, you will likely limit the changes to one place in your code.

Generics

The Java architects have traditionally gone to great lengths to ensure type safety, but there’s been one particular case that’s troubled them. When you take an object from a collection, you need to cast the object:

    ArrayList animals = new ArrayList();
    animals.add("elephant");
    String cat = (String)animals.get(0);

The compiler has just lost the ability to provide compile-time type safety. You could call the array element anything you want. To fix this, Java introduces an ugly implementation of a feature called generics . Here’s what the usage looks like:

    ArrayList<String> animals = new ArrayList<String>();
    animals.add("elephant");
    String elephant=animals.get(0);

Comparing the preceding code with its nongeneric equivalent, you may think that you avoided casting, but you really did not. Java introduced an ugly implementation of generics, called type erasure. Under the hood, in the modified version, the ArrayList still maintains a collection of Objects and not a collection of Strings. Of course, any library that you need to strongly type with user-defined types must enable the code for generics. Enabling generics gets a little ugly. Here’s the List declaration from within the Java collections package:

    public interface List<E> { void add(E x);
    Iterator<E> iterator();
    }public interface Iterator<E> {
      E next();
      boolean hasNext();
    }

If you’re not a fan of statically typed languages, you don’t like the extra type checks that place yet an additional burden on you. Even if you like the idea of generics, you probably don’t like the implementation. Generics offer only syntactic sugar and not real runtime protection, because the JVM has no concept of generics. In an article series on agiledeveloper.com,[*] Venkat Subramaniam lays out the problems in gory detail:

  • You lose type safety when you mix nongenerics with generics. For example, List notGeneric = genericList; type safety would not flow into notGeneric, even though it’s bound to the same list as genericList in memory.

  • You can’t use primitive types as parametric type or static fields of generic type.

  • Instances of different parameterized types (like ArrayList<String> and ArrayList<Book>) belong to the same type ArrayList.

  • Since the JVM has no notion of generics, other classes won’t be able to take advantage of generics via reflection.

So, if you’re protected at only a superficial level, and if new languages can’t participate in the solution, the syntax only serves to further burden users with details and inconsistencies, prompting the question, are generics a solution begging for a problem? When I ask my students how many class cast exceptions they get from collections, very few say this is a significant problem.

Overloading

In some ways, Java’s typing problems are exacerbated by another limitation described as a feature: method overloading . Taken alone, overloading is not a huge problem, but Java developers use overloading to enable an API that supports multiple types. You’ve got a surefire recipe for API bloat.

Need an example? Take the java.util.Array interface. Please. For convenience, you get more than 70 methods. Peel back the onion, and you see they cover only 10 or so pieces of actual, distinct functionality. With a smarter method declaration, you’d be able to specify parameters with keywords, and default unused parameters to an intelligent value, like 0 or null.

Other Costs

When you decide to type everything, it’s a slippery slope. When you need to pull back from Java’s typing system, you can’t always do so. You’re starting to see many examples of Java libraries working around the typing in unusual ways. Study the JMX interface for an excellent example. Does it use strong typing? It appears that way, at first. Then you dig in a little and find what only can be conceptually described as an embedded type system—a mini-language, embedded in a String parameter called ObjectID, with a complete language description in the JavaDoc and syntax completely opaque to compilers and interface generators and processors. Java’s type system failed here. JMX architects bypassed the type system, building metadata into strings and other objects. If you look around, you’ll find other examples of this as well. Most often, Java hides weaker types, or dynamic types, as strings.

The Benefits of Static Typing

After reading about all of the negatives, you’re probably wondering why anyone would ever opt for strong, static typing. There are at least two compelling reasons to do so. Static typing reduces certain types of errors (like misspelled variable names), and provides more information for your IDE and other tools. (Most security-related typing arguments refer to weak typing, not dynamic typing.)

Take the following application. Java will catch this error at compile time:

    int consumer;
    if (conusmer =  = 0) return consumer;  //spelling error

It’s hard to imagine a dynamic language, with rigorous unit testing, letting an error like this through, though. The IDE problem is a little bit more obscure. Many of the features that Java developers have come to depend on, like method completion, rely on information in a variable’s type. You can’t always get the same contextual information out of a Ruby or Smalltalk IDE.

A Safety Net with Holes

The Java founders most often cite the ability to catch type mismatch errors at compile time rather than runtime. That’s interesting to me, because of all the Smalltalk and Ruby developers I interviewed, few have ever had significant problems with type mismatch errors. Of course, most of them lean pretty heavily on automated unit testing, as we all should. You need to unit test code regardless of whether you use dynamic typing. No compiler can guess your intent perfectly. Even if you like the generics implementation, you’ve got to be concerned with an implementation that’s little more than syntactic sugar, with no JVM implementation behind it.

With the heavy use of test-driven development, the argument for reduced bugs is much less compelling. In fact, Java’s type safety is not as encompassing as the founders would lead you to believe. At any given time, most of the objects in a typical Java application reside in collections. Any time you remove one of these objects from its collection, you need to cast up from Object. You’re effectively retyping an object. If you cast it incorrectly, glass will break in the form of a class cast exception, at runtime. At the same time, improved tools and emphasis on automated unit testing make it much easier to catch type problems in dynamic languages long before they ever reach production. My experience tells me that Java’s type safety is not as important and comprehensive as most programmers think it is, and the typing in more dynamic languages, with unit testing, is not as limiting.

The IDE code completion problems presented by dynamic typing will probably get solved by a combination of better browsers and smarter context. Unit testing will make type safety less useful from a program correctness standpoint. In the end, for application programming, more dynamic typing will prevail. The productivity gains due to dynamic typing are too compelling to ignore.

Primitives

From the very beginning, Java designers consciously made decisions to attract the C++ community, and favor performance over other considerations. The biggest compromise was the inclusion of primitive types. This addition means Java is not fully object-oriented, and presents several significant challenges. Those who came from the C++ community don’t always see a problem, but developers from other programming languages often see primitives as an ugly kludge. Primitive types do not descend from Object, so Java is more of a hybrid language than a true object-oriented language. But that’s all academic. There’s a real cost associated with the theory.

Primitives Are Limited

Java primitives limit you because they don’t descend from a common Java object. One of the nice things about most object-oriented languages is polymorphism: you can deal with specific objects in a general way. In Java, that’s not quite true, because primitives do not descend from Object. You can’t, for example, say 6.clone(), or 6.getClass().

If you’ve ever built an XML emitter or an object relational mapper, you know about the headaches related to primitive support. In Java, you can’t treat all types the same, and you don’t have the benefit of natural methods on the primitive types. You have to build in explicit support for objects, primitives, and arrays.

Since most of us don’t build XML emitters or persistence frameworks, we shouldn’t care about those costs, right? It’s not that easy. You still have to deal with complications in the language, such as inconsistent APIs and added breadth of the frameworks that you do support. Reflection is probably the worst. To get the value of a field, you first have to determine the type. You then get the value, with one of get, getBoolean, getByte, getChar, getDouble, getFloat, getInt, getLong, or getShort. Of course, if it’s an array, all bets are off. Arrays can contain primitives or objects, so they can’t even treat their contents generically. You basically have to go through the whole process again.

Reflection in pure object-oriented languages is much simpler. To get a field’s value, you use a single API to query a field, and get an object back. You can then query the object to find the defining class. If you want to deal with it as a top-level object, you don’t even have to do that.

Primitives Are Unnaturally Verbose

Of course, you need to be able to do some things to a primitive that the primitive itself can’t do. Java solves this problem by providing type wrappers. Primitives are so awkward because sometimes you use the primitive and sometimes you use the wrapper. It’s very difficult to be consistent with usage.

When you add the additional wrappers and casts, you find that primitives don’t help make Java cleaner, and they make it only marginally faster. Since you have both types and wrappers, you often need to convert between the two, forcing unnecessary syntax, and often unpredictable behaviors (such as several strange behaviors in the autoboxing in Java 1.5).

The Big Trade-off

All in all, primitives were important in one sense: supporting them let Java aggressively attract C++ developers, because the idea and syntax were similar. In retrospect, though, it’s created some significant problems, in terms of language clarity, productivity, and readability.

In retrospect, we’re paying for the early compromises that it took to draw away the C++ community. That’s a fair trade, in my book. Don’t underestimate the cost, though. Primitives complicate the code base, lead to inconsistencies, and bloat the language. The next popular programming language will probably not be a hybrid language, with both objects and primitives. C++ started the transition to object-oriented programming and Java finished it. We don’t need a crutch anymore.

Parting Shots

Of course, you could write a whole book about the strengths and weaknesses of Java alone. I don’t think that’s productive. I won’t rehash the “EJBs stink” message that’s been presented prominently in my last three books. I also don’t want to launch into a debate about the meaning of whitespace, Java’s commenting styles, or the relative benefits or evils of byte code enhancement. Still, there are more things to cover. Exceptions and strings play a huge role in most Java applications.

Sun

Sun is not the company that it once was, placing Java’s future in doubt. I’m not saying that Java will disappear, but Sun might. It has lots of cash in the bank, but where is it going to make money? It’s being squeezed on the low end by companies like Intel, Dell, and AMD. IBM is squeezing Sun from above. Sun’s software and services businesses have never really taken off. I think Sun is a ripe acquisition target.

If Sun does have major problems, what happens to Java? I fear that an IBM acquisition would put too much emphasis on the hardest enterprise problems, moving Java further away from its base. Open sourcing Java could effectively splinter the language. Other potential suitors, like Oracle and BEA, could lead to a conflict of interest that could stymie new standards.

IBM may be getting nervous. It has begun to hedge its Java position in several ways:

  • IBM is aligning closely with BEA on standards like SDO, and it is increasingly at odds with the JCP. IBM may well be positioning itself to challenge the JCP, or establish standards outside of the JCP.

  • IBM looks like it may embrace PHP more closely, to take advantage of that swelling marketplace. PHP would be an effective hedge for smaller and intermediate businesses.

  • IBM continues to invest in XML technologies with Microsoft.

In any event, Sun’s ultimate health, or lack thereof, casts doubt on the shape of Java’s future. If you’re trying to maintain market dominance, uncertainty is not the best place to start.

Exceptions

Like static typing, Java’s emphasis on checked exceptions seems like it’s on unshakable footing. The argument goes something like this: if a typical developer doesn’t have to deal with an exception explicitly, he probably won’t deal with it at all. For me, and for many of my customers, checked exceptions tend to hurt more than they help, for many reasons:

  • The exception syntax is incredibly invasive. Exceptions easily dominate a typical method, even at very low levels, when you can’t do anything about them.

  • Most of the time, you can’t deal with an exception, so you can only throw it up the chain anyway. You shouldn’t have to do a job explicitly that the compiler can do for you.

  • Having so much exception syntax deadens you to the few lines of exception code that actually do something important. Said another way, it’s hard to see the diamond through all the mud.

Recently, Java frameworks like Spring and AspectJ have begun to recognize the power of unchecked exceptions. Hibernate founder Gavin King has often said that he would have built Hibernate on an unchecked exception model if he had a chance to do it over again. Hibernate converted to unchecked exceptions at Version 3.

Expressing Data

Programming and data go hand in hand. In most other languages, structured data becomes a natural part of an application. Part of Java’s over-reliance on XML comes from its limited ability to express structured data. In Ruby, I can quickly declare a hash map of arrays, for example. Such structures dramatically ease configuration and allow natural metaprogramming.

Strings

If you look at Perl , you can quickly understand what it’s designed to do. It’s a turbo-charged text manipulation engine. Though it’s very complicated in other ways, Perl has been so popular because it does what it’s designed to do.

By contrast, if you look at Java, you don’t have the same convenient, high-powered text manipulation. That’s surprising, especially when you look at the core job that we ask Java to do. Servlets, XML, JSP, HTML, and many other constructs are strings. In fact, I probably work with strings in some form more often than I do anything else. It’s amazing to me that Java’s not any better than it is when it comes to strings. Its pattern-matching support is second class, and the major string APIs are at an extremely low level.

Simplicity

Java’s already a good language for big, hard-core enterprise programming projects. It’s getting better at solving that problem. And Java’s never been good at tiny applications that you might write for a small business in Visual Basic . There’s a huge middle ground between these two problems. Java stepped into this gap with a vengeance and ripped the heart out of Microsoft’s enterprise programming community. But Figure 4-2 shows Java is leaving that community behind rapidly.

Java has controlled the gap between enterprise projects and small ones, but is now leaving that community behind
Figure 4-2. Java has controlled the gap between enterprise projects and small ones, but is now leaving that community behind

In my past three books, I’ve made the case that Java has to get simpler to thrive. That’s not happening. Java’s power structure is entrenched firmly in the enterprise space. In the past three Java One conferences, Sun has paid lip service to the need to simplify the Java API, but we’re seeing only limited focus on richer user interfaces. The big vendors claim a drive to simplification, but the ultimate answer is EJB 3.0 , generics , and Java Server Faces (JSF) .

In fact, Java is moving away from its base. Remember, huge numbers of us are waiting for better, simpler ways to baby-sit a relational database with a web frontend. Instead, we’re seeing more XML, more configuration, more layers of abstraction, and a steady drift away from the user interface and the end user. Java takes longer to learn and is no longer approachable.

Tools

One of the symptoms to this problem is Java’s over-reliance on tools. We Java developers love our IDEs. The truth is that we can’t live without them. In the not-too-distant past, I did some research for a major application server vendor. I found that the most productive developers liked the command line better. You can always find a command line, and an editor. If you’re comfortable with these tools, you can go anywhere.

But in the past three years, we reached a tipping point of sorts. The smartest developers are moving toward IDEs, because the language has become too complex to manage without them. You simply need an IDE to do any real degree of refactoring. Other languages have IDEs, and also good programmers who are very comfortable without them.

Why Not Just Fix Java?

You might argue that we need to fix Java, not scrap it. That would be easy if you could pinpoint the problems. If you thought the problems were in the language itself, you could just do some major surgery and offer a new version of Java. That’s easier said than done. Sun has been very careful to preserve backward compatibility at all costs. If you look at the lack of commercial acceptance for Visual Basic .NET, it’s easier to respect Sun’s point of view. Microsoft made some radical changes to the language and libraries , and they weren’t well received. Regardless of whether it’s a good idea, Sun will continue to be conservative to protect customers.

Still, even if you look at relatively aggressive changes, most experts that I interviewed tend to think Sun is even now moving in the wrong direction. Instead of making Java more nimble and dynamic at the foundation, the changes, for the most part, amount to additional type safety—syntactic sugar hacks built into the syntax rather than the JVM that often lead to inconsistent behavior.

Libraries and Community

It’s clear that libraries are problems, too. Sun has launched several belated simplification movements. So, if it’s Java’s libraries that are broken, and not the language itself, couldn’t we just scrap a few libraries and start over on a more simplified foundation? That’s the approach we suggested in Better, Faster, Lighter Java. For Java’s most important and basic job, a web-based user interface on a relational database, I don’t think Java’s frameworks are moving in a healthy direction at all. Most frameworks are moving to add more compelling features rapidly, instead of simplifying what’s already out there.

One bad library might point to a few local mistakes. Java’s problems are more global. They target very complex problems at the expense of the easy problems that most Java developers need to solve. The problem is clear. The Java leadership is abandoning its base willingly and rapidly. It’s a cultural problem, inherent in the Java community, vendors, programmers, and leadership. Java has become strictly a language for hard-core enterprise development of large-scale problems.

Alternatives

Over the next five years or so, the question in play will be this: are the Java community and expansive code library base worth sacrificing the productivity that other alternatives will bring to bear? So far, the answer has been a resounding “Yes.” But we’re nearing a point of no return. Java needs radical changes if it wants to continue to be all things to all people, but the community, culture, and leadership behind Java have never produced the kind of structural, sweeping changes that we need. Sun has always treated Java conservatively. The community process has always built the kind of software you’d imagine a community process would build: bloated compromises that please no one in the end. The Java community has always tolerated too much architecture, too much XML, and too many layers.

In the second half of this book, I make the case that a clean, dynamic language could gain footing easily in the gap between Visual Basic and enterprise Java. Once entrenched, it could take the same path Java did, into the enterprise. After all, the lion’s share of Java development, even in the enterprise, is not full of distributed transaction and backbreaking loads. Five years ago, most developers that I talked to on a regular basis wanted a good way to baby-sit a big, fat relational database with a web-based user interface. Five years later, they want the same thing.

So far, I’ve shown you how Java is drifting away from its base. In the next chapter, you’ll see the rules of the game for the next major successful language. In the next half of the book, I’ll explore what alternative languages have to offer, and whether that will be enough to take you beyond Java.



[*] Venkat Subramaniam. Generics in Java, parts 1-3 (June 2005); http://www.agiledeveloper.com/articles/GenericsInJavaPartI.pdf, ...GenericsInJavaPartII.pdf, GenericsInJavaPartI.pdf.

Get Beyond Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.