Every language is portable, as long as you always use the same machine platform.
With C/C++, this means using the same physical machine platform and the same operating system, because C/C++ compiles to native code.
With Java, this means using the same virtual machine, because Java compiles to bytecode that is run on a VM, which meanwhile is compiled to native code, thus providing a “bridge” between bytecode and the physical machine.
Similarly, with Perl, this means using the same JIT-compiler/interpreter environment. With Ruby, the same interpreter environment. Et cetera.
The reason C/C++ and Perl both seem more portable than Java to everyone except those writing internal enterprise applications for environments entirely within the control of the IT department’s policy decisions is simple:
C/C++ is portable because the compiler can turn the same source code into native machine code for any machine. Perl is portable because the JIT-compiler/interpreter environment is the same on every platform.
Java is not so portable because Sun screwed around with licensing and hidden specs for so long that eventually people started trying to reverse engineer it to produce pseudo-workalikes, and the end result is a bunch of incompatible VMs. This wouldn’t be regarded as such a big deal if it wasn’t for the fact that Java is distributed in bytecode form with the “write once, run anywhere” marketing hype attached to produce unrealistic expectations of supreme portability. Now, Sun is finally releasing Java as open source software. Sun has settled on an open source license: the GPL (a problem, but not to be addressed at this exact moment). Too little, too late — the damage is done.
If you really want your Java to run “anywhere”, you need to compile it to bytecode separately for every VM available for it. Then, of course, you need to convince your users to pay attention to what they’re using to the extent that they can choose the correct bytecode-compiled Java application blob.
It’s difficult enough getting people to notice what hardware architecture, operating system family, and version of OS they are using so that they can pick out the right software. Now, you’re asking them to remember what VM, and what version of it, they are using just for the sole purpose of selecting the correct application install or download to use just for applications written in one stinkin’ language. Languages should not even be recognizable to the end user. The end user should not be able to differentiate between a system utility or application written in C, C++, OCaml, Haskell, Java, Ruby, Common Lisp, Perl, or Erlang. Out of all those languages, I can have a program written in any one of them sitting on my hard drive on this Linux system that has its executable bit set that I can run just by calling it by name, without having to worry about whether I have the right version of the parser on my system — except one.
Write once, run nowhere.