They are in fact quite similar; each specifies an API that something must implement — and must declare that it does implement — in order to be usable in that context. You might, for example, define an interface/trait for Stack<T> and then write implementations for it.
I can think of two significant differences, though, between how that works in Java and how it works in Rust.
Important difference in how you make a type compatible with an API
The first is in the way the implementation is defined. Java is somewhat (not completely) object-oriented, so to create an implementation you change the class definition to make it implement the interface. So there are two entities involved: the interface and the class that implements it. In Rust, as in Haskell, there are *three* entities: the trait, the type, and a separate “implementation” that makes the type compatible with the trait; that’s where the API methods/etc. go. This has the huge advantage of making it possible to make somebody else’s type compatible with a trait you define. In Java you have to modify a class’s definition in order to make it implement an interface, so if it’s defined in somebody else’s library, you’re out of luck.
The Rust/Haskell way has another advantage, too. In Java if you want a class to implement two interfaces that each have a method foo(), and they are not identical, then you are out of luck — your class can implement one of the interfaces but not both. But in Rust/Haskell that’s no problem — the implementations for the two traits are completely separate, so you write the appropriate foo() for each; they have nothing to do with one another.
Difference in how the generated code calls an API function/method
The second difference is in the way calls to the interface/trait happen. The Java byte code for such a call will use the InvokeInterface instruction, which has to find the interface implementation — its “vtable” — for the object. That means going to the class definition for the object and rummaging through its interface implementations. Done naively, this is expensive, so when the JVM compiles your code it tries to optimize this, and often it can. If there is only one class that implements that interface in your program, then the compiler will just call that class’s implementation directly, so you won’t have *any* performance penalty. But if there are multiple implementors, then there will be some runtime overhead involved in figuring out which code should actually run. In the worst case there are several implementations that are all frequently used, in which case the code has to check the class’s interface table entry by entry to find the correct vtable (table of pointers to methods) before it can make the call. That gets expensive for a frequently invoked method.
In Rust, what happens comes in two flavors. Most commonly you write the code using generics, in which case the compiler creates a separate version of the code for each implementor you actually use with the trait. Clearly calls to such code have no overhead — it’s just a regular function call to whichever specialization (version of the generic code for a particular type) is appropriate.
Alternatively you can specify that a trait parameter is “dynamic,” meaning that you want the vtable passed in along with the reference and used in making calls. This is slower because the functions cannot be inlined (inlining generally creates opportunities for optimization), but at least it doesn’t have to work to find the vtable. There are reasons why it makes sense to do this (like reducing program size) when the impact on performance is slight. It’s entirely up to you which approach to use in each case.
If I’ve forgotten other key differences, please let me know in the comments.