Bonus module: Traits
This is an optional bonus module that builds directly on the scanning recipe you wrote in Module 3. Modules 1–3 cover the core advanced workshop content — complete those first, then come back here if you want to go further.
Traits are a higher-level abstraction over OpenRewrite's LSTs (Lossless Semantic Trees) that let you build reusable logic for elements that are semantically similar but structurally different. Instead of embedding utility logic in unrelated classes or expanding the core LST APIs, traits act as opt-in behavior layers — keeping your recipes modular, discoverable, and semantically rich. For example, to operate on every class annotated with @Bean regardless of structure or placement, a trait can define a single matcher that groups them all together. You'll work through that example next.
Exercise A: Explore a recipe that uses Traits
In this exercise, you'll use the Annotated.Matcher trait that the FindSpringBeans recipe uses to identify classes annotated with @Bean and marks them using SearchResult.found.
Goals for this exercise
- Understand the purpose of traits and when to use them.
- See how the
Annotated.Matchertrait is used to build reusable match logic.
Steps
- In the
rewrite-recipe-starterproject, openFindSpringBeans.javaand read the comments describing how traits are used. Note thatgetVisitor()returns anAnnotated.Matcher(OpenRewrite'sAnnotatedtrait matches annotations and annotated elements) andSearchResult.foundis used to mark matches and write them to a data table. - Open
FindSpringBeansTest.javaand note theSearchResultmarker syntax in the expected after-blocks (/*~~(bean)~~>*/), plus theRecipeSpecassertThatthat verifies the data table contents (data tables are covered in Module 2).
Takeaways
- Traits provide higher-level abstraction over raw LSTs.
- The
Annotated.Matchertrait can be used for annotation matching. - Recipes that use traits can be more modular and maintainable.
Exercise B: Write a recipe using traits
The Java-only TODO scanning recipe from Module 3 could be extended to handle XML and YAML by adding more visitors and matching rules, but a trait is a cleaner fit — it encapsulates the cross-language match logic in one reusable place. In this exercise, you'll write a TodoComment trait that matches TODO comments across Java, YAML, and XML, then build a recipe that uses it to collect those comments into a file and a data table.
Goals for this exercise
- Learn how to define a Trait to encapsulate cross-language patterns in the LST.
- Understand how Matcher classes generalize visitor logic across multiple source types.
- See how traits simplify scanning recipe logic by providing a consistent matching abstraction.
Steps
- Open the unit test
TrackTodosTest.java. The first two tests should look familiar from Module 3; the additional tests cover XML and YAML cases, and theSourceSpecsindicate which file type (java,yaml, orxml) is being tested. Remove the@Disabledannotations and run the tests — they fail. - Open
trait/TodoComment.java— aTraitwithcursorandtodosmembers and a nestedMatcherextendingSimpleTraitMatcher. Thetest(Cursor cursor)method is partially filled in: it reads the cursor's value and branches on its LST type to decide how to match a comment. - Fill in each section to match
TODOcomments for that file type. For Java, you can borrow theJavaIsoVisitorlogic fromTrackJavaTodosFile.getScanner(...)(Module 3). For XML and YAML, explore the LST model with the debugger orTreeVisitingPrinterfrom Module 1.- Hint: For YAML, look at
getPrefix(...). For XML, look atXml.Prolog.getMisc()andXml.Tag.getContent().
- Hint: For YAML, look at
- Open the recipe template
TrackTodos.javaand write a scanning recipe that collectsTODOcomments from Java, XML, and YAML files intoTODO.mdand also records each comment in a data table (as in Module 2).getScanner()is simpler than in Module 3: a singleTreeVisitorusingTodoComment.Matcher()handles all three file types, so you no longer need two visitor types.generate()is identical toTrackJavaTodosFile.generate()from Module 3, andgetVisitor()is nearly identical — you'll just need to flatten what is now a list of lists of comments.
- Build your project and run the tests. They should all pass. If any fail, use the failure description to locate the problem.
- If you get stuck, see the completed
TrackTodos.javaandTodoComment.javafor reference.
Takeaways
- Traits help express reusable logic across multiple source types (e.g. Java, XML, YAML).
- Instead of writing multiple visitor implementations, you can use a single matcher to find relevant nodes.
- Scanning recipes can become simpler and more maintainable when combined with traits.