Boolean logic and indirection
Joel's diatribe on JavaSchools in my opinion focuses too much on the tools used in education (Java vs Lisp for instance), instead of the fundamental skills required by software developers. Also recursion should be used with caution in production systems. I don't think it is a good benchmark for the effectiveness of developers. If C or C++ is your implementation language, it is easy for recursive functions to result in stack overflow which is undefined by both the C and C++ specs. It is nearly impossible to recover from stack errors.
Back in college, I tutored first year CS students, and in my opinion there were two hurdles students had to cross to get to upper level courses. Boolean logic and indirection. I vaguely recall reading that only 25% of the population can comprehend boolean logic, and after attempting to explain loop exit conditions, De Morgan's theorem, and boolean assignments, my intuition supports this. There is nothing wrong with not being able to understand boolean logic. There are plenty of skills that I can't comprehend (grammar for instance ;) ), but those who can't grapple with basic if conditions should consider a career outside of software. They just aren't going to be happy in this field.
All the kids who did great in high school writing pong games in BASIC for their Apple II would get to college, take CompSci 101, a data structures course, and when they hit the pointers business their brains would just totally explode...The problem isn't understanding pointers, it's understanding indirection. Indirection is everywhere in software. A common example is Word's styles. Many users understand changing the font on a piece of text directly, but fail to see how changing the font indirectly on a style can also affect the rendering of text. I've actually considered this problem when implementing similar functionality. While many power users find the functionality useful, there is a large class of users who simply don't understand the indirection.
Pointers don't hold data values directly. Instead they reference addresses to the memory location which holds the data. The pointer needs to be dereferenced to access the data it points to. That's indirection. Foreign keys in relational databases are also indirection. They are references to records in other tables. The data is accessed indirectly through the foreign key. While developers might get away with not understanding pointers, eventually indirection is going to catch up with them. That's why pointers are used to weed out developers in the first year -- not because pointers are the most important concept in software, but because they are a concrete instantiation of the indirection problem.
Also, indirection and recursion aren't the hard problems anymore. With clock speeds hitting a wall, multi-core systems are becoming the solution to improving computing performance. Concurrency has become the big problem, and Java is a decent language for concurrent programming. Unlike C and C++, as of version 1.5, Java has a specified memory model, and threading primitives based monitors and condition variables. This is important to determine how data is written to the memory bus and processor caches in multi-processor or multi-core systems.
While I agree with Joel that to play ball in the software world you have to study the rudiments, I just don't care if developers come to that understanding through Java, ML, prolog, or Scheme. With an understanding boolean logic, indirection, and possibly concurrency, all that is left is syntax.