It's simple, you simply implement some sort of code signing feature, or you only let people run arbitrary code in a sandbox or some sort of similarly limited environment. Letting people load code from a remote source into the same address space of a running program containing potentially sensitive data is intrinsically bad.
In fairness, Java has had capabilities to only let signed code run since its early days (it was fundamental for stuff like Applets or RMI), but like the rest of Java the whole specification is over engineered and quite complicate so nobody bothers with it. There are just too many old features lying around in the JRE that should be off by default. Like they shouldn't be possible to use unless you explicitly configure your environment to enable them. 99.999% of apps do not need and will never care about JNDI, and having it just lying around doing nothing just pointlessly increases the surface attack of your application for no good reason.
In fairness, Java has had capabilities to only let signed code run since its early days (it was fundamental for stuff like Applets or RMI), but like the rest of Java the whole specification is over engineered and quite complicate so nobody bothers with it. There are just too many old features lying around in the JRE that should be off by default. Like they shouldn't be possible to use unless you explicitly configure your environment to enable them. 99.999% of apps do not need and will never care about JNDI, and having it just lying around doing nothing just pointlessly increases the surface attack of your application for no good reason.