When you're doing too much design to solve a basic problem, you're over-engineering. For example, when you're using an abstract factory pattern “just in case” in order to create a very simple object where the business logic is easy enough to fit in four-five LOC, you're over-thinking the design (and violating KISS and YAGNI).
Note that it doesn't necessarily need to be overly complex. An abstract factory pattern has nothing complex in it as soon as classes are named correctly and the other members of the team know the pattern. This being said, it's still too much classes and code for a problem which can be solved without recurring to any specific pattern.
When you're not doing enough, you're under-engineering. For instance, if most of your Java code base is in static methods within static classes and the major part is in utility classes, you are not thinking enough about the design.
Notice that over-engineering and under-engineering is usually done by inexperienced programmers, through two different patterns:
The most inexperienced ones don't know design patterns enough and tend to not use them and not think too much about the design. They let their code grow with little or no design, finding such code simpler to read and understand.
Slightly more experienced ones who started to learn design patterns have a tendency of applying those patterns everywhere to show they know them. Should we create an object? Let's use a builder pattern with an abstract builder and a concrete one. Need to communicate between two objects? Let's use adapter pattern! And, obviously, fluent interfaces are fun, since it makes us write code like this:
Product product = ProductBuilder()
.setDescription("Description goes here")
Price price = new Price(59, Unit.Dollar);
Product product = new Product(39, price, "Product 1", "Description goes here");
It requires much more skill and experience to find the golden middle, that is to use as much design as needed, but absolutely no more. This is what right-engineering term is about, although it's not used that much in the industry, for a good reason.
Over-engineering and under-engineering are the terms which are used to criticize someone else's code, like in “Dude, stop over-engineering your code, we can barely find our way through dozens of classes you add unnecessarily!” On the other hand, the golden middle of right-engineering of a developer won't necessarily be the same (and is usually not the same) for others. A recent answer shows how non-obvious is it to determine where to put a frontier between using a factory or moving the logic in a constructor. Thus, one can't assert that his code is right-engineered, because at least some of his colleagues would disagree.