The AbstractProcessor below processes greghmerrill's @Unsafe annotation and emits warnings on method calls to @Unsafe annotated methods.
It is a slight modification of greghmerrills own answer, which was great, but I had some problems getting my IDEs incremental compiler (I am using Netbeans) to detect the warnings/errors etc emitted from the plugin - only those I printed from the processor was shown, though the behaviour was as expected when I ran 'mvn clean compile' ( I am using Maven). Whether this is due to some problem from my hand, or a points to difference between Plugins and AbstractProcessors/the phases of the compilation process, I do not know.
Anyway:
package com.hervian.annotationutils.target; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.util.*; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeInfo; import java.util.Set; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.tools.Diagnostic; @SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"}) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener { Trees trees; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); trees = Trees.instance(processingEnv); JavacTask.instance(processingEnv).setTaskListener(this); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //Process @Unsafe annotated methods if needed return true; } @Override public void finished(TaskEvent taskEvt) { if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) { taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() { @Override public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) { Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect()); Unsafe unsafe = method.getAnnotation(Unsafe.class); if (unsafe != null) { JCTree jcTree = (JCTree) methodInv.getMethodSelect(); trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit()); } return super.visitMethodInvocation(methodInv, v); } }, null); } } @Override public void started(TaskEvent taskEvt) { } }
When using the annotation and making calls to the annotated method it will look like this: 
One needs to remember to add the fully qualified class name of the annotation processor to a META-INF/service file named javax.annotation.processing.Processor. This makes it available to the ServiceLoader framework.
Maven users having trouble with the com.sun** imports may find this answer from AnimeshSharma helpful.
I keep my annotation + annotation processor in a separate project. I had to disable annotation processing by adding the following to the pom:
<build> <pluginManagement> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgument>-proc:none</compilerArgument> </configuration> </plugin> </plugins> </pluginManagement> </build>
Using the annotation and having the processor do its work was simple: In my other project (the one where the screenshot of method foo() is from) I simply added a dependency to the project containing the annotation and processor.
Lastly it should be mentioned that I am new to AbstractProcessors and TaskListeners. I do, fx, not have an overview of the performance or robustness of the code. The goal was simply to "get it to work" and provide a stub for similar projects.
Foowas implementing an interface, with the@Unsafeannotation on just theFooimplementation. Then a client using the interface wouldn't show up.@Unsafe. Thus, the compile-time processing is complete and sound.@Unsafebehaves exactly like@Deprecated. I see your point @sisyphus, you are saying@Unsafeshould act kind of like the "synchronized" modifier, i.e. you really must have the implementation in-hand to know whether it's synchronized or not. But to simplify, let's assume that this works exactly like@Deprecated, i.e. I can just look at the compiled type and not worry about runtime substitutions.