{"id":189,"date":"2026-10-12T17:01:21","date_gmt":"2026-10-12T17:01:21","guid":{"rendered":"http:\/\/www.scmgalaxy.com\/tutorials\/2010\/03\/09\/junit-4-test-logging-tips-using-slf4j\/"},"modified":"2026-10-12T17:01:21","modified_gmt":"2026-10-12T17:01:21","slug":"junit-4-test-logging-tips-using-slf4j","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/junit-4-test-logging-tips-using-slf4j\/","title":{"rendered":"JUnit 4 Test Logging with SLF4J: Cleaner Output with Rules and Parameterized Messages"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Originally published on 2010-03-09; fully revised and updated.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your JUnit 4 tests still rely on ad-hoc <code>System.out.println()<\/code> lines, this pattern gives you a cleaner path: use SLF4J for consistent logging, add parameterized messages for safer performance, and centralize test lifecycle logs with JUnit rules. This is useful for developers who want clearer local debugging and less noisy CI test output.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2010\/03\/junit-4-test-logging-tips.png\" alt=\"JUnit 4 test logging using SLF4J\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Why test logging quality matters in JUnit 4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quick print statements are easy at first, but they become hard to maintain across many tests. Structured logging improves readability, supports log levels, and lets teams route logs through their existing logging backend without changing test code.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Better signal:<\/strong> consistent format and severity levels.<\/li><li><strong>Less duplication:<\/strong> avoid repeating method names in every test body.<\/li><li><strong>Backend flexibility:<\/strong> SLF4J lets projects choose Logback, Log4j, or another binding later.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Minimal dependency setup for the examples<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If your project does not already include JUnit and SLF4J, a Maven setup typically includes JUnit, <code>slf4j-api<\/code>, and one SLF4J binding implementation.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>junit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>junit<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>YOUR_JUNIT4_VERSION<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">scope<\/span>&gt;<\/span>test<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">scope<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.slf4j<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>slf4j-api<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">version<\/span>&gt;<\/span>YOUR_SLF4J_VERSION<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">version<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n\n    <span class=\"hljs-comment\">&lt;!-- Choose one SLF4J binding implementation for runtime\/test runtime --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependencies<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Logging approaches compared<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Approach<\/th><th>Example<\/th><th>Strengths<\/th><th>Trade-offs<\/th><\/tr><\/thead><tbody><tr><td>System.out.println()<\/td><td><code>System.out.println(&quot;testA being run...&quot;);<\/code><\/td><td>Fastest to write<\/td><td>No levels, no backend control, easy to overuse<\/td><\/tr><tr><td>Direct SLF4J in each test<\/td><td><code>logger.info(&quot;testA being run...&quot;);<\/code><\/td><td>Structured logging, backend-agnostic<\/td><td>Repeated strings and repeated lines in each method<\/td><\/tr><tr><td>SLF4J + TestName rule<\/td><td><code>logger.info(&quot;{} being run...&quot;, name.getMethodName());<\/code><\/td><td>No hard-coded method names<\/td><td>Still repetitive if repeated in every test<\/td><\/tr><tr><td>SLF4J + watcher rule<\/td><td><code>starting\/succeeded\/failed hooks<\/code><\/td><td>DRY lifecycle logging across all tests<\/td><td>Rule API details depend on JUnit 4 style used in the codebase<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-step refactor from println to reusable lifecycle logging<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1) Baseline: println logging<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-java\"><span class=\"hljs-keyword\">import<\/span> org.junit.Test;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoggingTest<\/span> <\/span>{\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testA() {\n        System.out.println(<span class=\"hljs-string\">\"testA being run...\"<\/span>);\n    }\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testB() {\n        System.out.println(<span class=\"hljs-string\">\"testB being run...\"<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">2) Switch to SLF4J<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-java\"><span class=\"hljs-keyword\">import<\/span> org.junit.Test;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.Logger;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.LoggerFactory;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoggingTest<\/span> <\/span>{\n\n    private <span class=\"hljs-keyword\">static<\/span> final Logger logger = LoggerFactory.getLogger(LoggingTest.class);\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testA() {\n        logger.info(<span class=\"hljs-string\">\"testA being run...\"<\/span>);\n    }\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testB() {\n        logger.info(<span class=\"hljs-string\">\"testB being run...\"<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">3) Remove hard-coded names with TestName and parameterized messages<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-java\"><span class=\"hljs-keyword\">import<\/span> org.junit.Rule;\n<span class=\"hljs-keyword\">import<\/span> org.junit.Test;\n<span class=\"hljs-keyword\">import<\/span> org.junit.rules.TestName;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.Logger;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.LoggerFactory;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoggingTest<\/span> <\/span>{\n\n    @Rule\n    public TestName name = <span class=\"hljs-keyword\">new<\/span> TestName();\n\n    private <span class=\"hljs-keyword\">static<\/span> final Logger logger = LoggerFactory.getLogger(LoggingTest.class);\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testA() {\n        logger.info(<span class=\"hljs-string\">\"{} being run...\"<\/span>, name.getMethodName());\n    }\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testB() {\n        logger.info(<span class=\"hljs-string\">\"{} being run...\"<\/span>, name.getMethodName());\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">4) Apply DRY with a watcher rule<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The original pattern used <code>TestWatchman<\/code>. Many JUnit 4 codebases use <code>TestWatcher<\/code> for similar lifecycle hooks. Use the style that matches your project\u2019s JUnit 4 API.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-java\"><span class=\"hljs-keyword\">import<\/span> org.junit.Rule;\n<span class=\"hljs-keyword\">import<\/span> org.junit.Test;\n<span class=\"hljs-keyword\">import<\/span> org.junit.rules.MethodRule;\n<span class=\"hljs-keyword\">import<\/span> org.junit.rules.TestWatchman;\n<span class=\"hljs-keyword\">import<\/span> org.junit.runners.model.FrameworkMethod;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.Logger;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.LoggerFactory;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoggingTest<\/span> <\/span>{\n\n    private <span class=\"hljs-keyword\">static<\/span> final Logger logger = LoggerFactory.getLogger(LoggingTest.class);\n\n    @Rule\n    public MethodRule watchman = <span class=\"hljs-keyword\">new<\/span> TestWatchman() {\n        @Override\n        public <span class=\"hljs-keyword\">void<\/span> starting(FrameworkMethod method) {\n            logger.info(<span class=\"hljs-string\">\"{} being run...\"<\/span>, method.getName());\n        }\n    };\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testA() {}\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testB() {}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-java\"><span class=\"hljs-keyword\">import<\/span> org.junit.Rule;\n<span class=\"hljs-keyword\">import<\/span> org.junit.Test;\n<span class=\"hljs-keyword\">import<\/span> org.junit.rules.TestWatcher;\n<span class=\"hljs-keyword\">import<\/span> org.junit.runner.Description;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.Logger;\n<span class=\"hljs-keyword\">import<\/span> org.slf4j.LoggerFactory;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">LoggingTest<\/span> <\/span>{\n\n    private <span class=\"hljs-keyword\">static<\/span> final Logger logger = LoggerFactory.getLogger(LoggingTest.class);\n\n    @Rule\n    public TestWatcher watcher = <span class=\"hljs-keyword\">new<\/span> TestWatcher() {\n        @Override\n        protected <span class=\"hljs-keyword\">void<\/span> starting(Description description) {\n            logger.info(<span class=\"hljs-string\">\"{} being run...\"<\/span>, description.getMethodName());\n        }\n\n        @Override\n        protected <span class=\"hljs-keyword\">void<\/span> succeeded(Description description) {\n            logger.info(<span class=\"hljs-string\">\"{} passed\"<\/span>, description.getMethodName());\n        }\n\n        @Override\n        protected <span class=\"hljs-keyword\">void<\/span> failed(Throwable e, Description description) {\n            logger.error(<span class=\"hljs-string\">\"{} failed\"<\/span>, description.getMethodName(), e);\n        }\n    };\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testA() {}\n\n    @Test\n    public <span class=\"hljs-keyword\">void<\/span> testB() {}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Watcher-based logging workflow<\/h2>\n\n\n\n<pre class=\"mermaid\">\nflowchart TD\n    A[JUnit starts test class] --> B[Rule starting hook logs method]\n    B --> C[Test method executes]\n    C --> D{Test outcome}\n    D -- passed --> E[Rule succeeded hook logs pass]\n    D -- failed --> F[Rule failed hook logs error]\n    E --> G[Rule finished hook runs]\n    F --> G\n    G --> H[JUnit reports result]\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Performance note on parameterized logging<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original article cites SLF4J guidance that parameterized logging can be much more efficient than string concatenation when a log level is disabled (quoted there as at least a 30x factor in that case). Treat that number as context from older documentation, and validate with your own runtime and logging configuration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reference and attribution<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Original post: <a href=\"https:\/\/www.devopsschool.com\/blog\/junit-4-test-logging-tips-using-slf4j\/\">https:\/\/www.devopsschool.com\/blog\/junit-4-test-logging-tips-using-slf4j\/<\/a><\/li><li>Referenced by the original author: <a href=\"http:\/\/www.catosplace.net\/\" target=\"_blank\" rel=\"noopener\">http:\/\/www.catosplace.net\/<\/a><\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Prefer <strong>SLF4J parameterized messages<\/strong> over string concatenation in log calls.<\/li><li>Centralize repeated test lifecycle logs in a <strong>single rule<\/strong> to keep test bodies focused on assertions.<\/li><li>Log <strong>start, pass, and fail<\/strong> events if CI diagnostics are important.<\/li><li>Keep logging at appropriate levels (<code>info<\/code> for lifecycle, <code>error<\/code> for failures).<\/li><li>Confirm which JUnit 4 rule API your codebase standardizes on before refactoring.<\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This guide shows how to evolve JUnit 4 test logging from println debugging to structured SLF4J logs using rules and parameterized messages. You\u2019ll see side-by-side approaches, lifecycle flow, and production-friendly example code.<\/p>\n","protected":false},"author":1,"featured_media":4134,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[60],"tags":[1122,1806,3433,3434,3445,3441,3436,984,3437,3439,3440,3438,3435,430,3442,3443,3444,1097,213],"class_list":["post-189","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-testing-tools","tag-guide","tag-instruction","tag-junit","tag-junit-4","tag-junit-4-and-slf4j-implementation","tag-junit-4-test-using-slf4j","tag-junit-tests","tag-log","tag-log-statements","tag-logging-techniques","tag-logging-tips","tag-simple-logging-facade-for-java","tag-slf4j","tag-test","tag-test-logging","tag-test-logging-techniques","tag-test-logging-tips","tag-tips","tag-tutorial"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/189","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=189"}],"version-history":[{"count":4,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/189\/revisions"}],"predecessor-version":[{"id":79019,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/189\/revisions\/79019"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media\/4134"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=189"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=189"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=189"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}