diff --git a/src/Standards/Generic/Docs/Strings/UnnecessaryHeredocStandard.xml b/src/Standards/Generic/Docs/Strings/UnnecessaryHeredocStandard.xml
new file mode 100644
index 0000000000..e0ca14f470
--- /dev/null
+++ b/src/Standards/Generic/Docs/Strings/UnnecessaryHeredocStandard.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ <<<'EOD'
+some text
+EOD;
+ ]]>
+
+
+ <<
+some text
+EOD;
+ ]]>
+
+
+
+
+ <<<"EOD"
+some $text
+EOD;
+ ]]>
+
+
+ <<<"EOD"
+some text
+EOD;
+ ]]>
+
+
+
diff --git a/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php b/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php
new file mode 100644
index 0000000000..bf923ebe56
--- /dev/null
+++ b/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php
@@ -0,0 +1,97 @@
+
+ * @copyright 2024 PHPCSStandards and contributors
+ * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Strings;
+
+use PHP_CodeSniffer\Files\File;
+use PHP_CodeSniffer\Sniffs\Sniff;
+
+class UnnecessaryHeredocSniff implements Sniff
+{
+
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @return array
+ */
+ public function register()
+ {
+ return [T_START_HEREDOC];
+
+ }//end register()
+
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
+ * @param int $stackPtr The position of the current token in
+ * the stack passed in $tokens.
+ *
+ * @return void
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ if (isset($tokens[$stackPtr]['scope_closer']) === false) {
+ // Just to be safe. Shouldn't be possible as in that case, the opener shouldn't be tokenized
+ // to T_START_HEREDOC by PHP.
+ return;
+ }
+
+ $closer = $tokens[$stackPtr]['scope_closer'];
+ $body = '';
+
+ // Collect all the tokens within the heredoc body.
+ for ($i = ($stackPtr + 1); $i < $closer; $i++) {
+ $body .= $tokens[$i]['content'];
+ }
+
+ $tokenizedBody = token_get_all(sprintf("", $body));
+ foreach ($tokenizedBody as $ptr => $bodyToken) {
+ if (is_array($bodyToken) === false) {
+ continue;
+ }
+
+ if ($bodyToken[0] === T_DOLLAR_OPEN_CURLY_BRACES
+ || $bodyToken[0] === T_VARIABLE
+ ) {
+ // Contains interpolation or expression.
+ $phpcsFile->recordMetric($stackPtr, 'Heredoc contains interpolation or expression', 'yes');
+ return;
+ }
+
+ if ($bodyToken[0] === T_CURLY_OPEN
+ && is_array($tokenizedBody[($ptr + 1)]) === false
+ && $tokenizedBody[($ptr + 1)] === '$'
+ ) {
+ // Contains interpolation or expression.
+ $phpcsFile->recordMetric($stackPtr, 'Heredoc contains interpolation or expression', 'yes');
+ return;
+ }
+ }//end foreach
+
+ $phpcsFile->recordMetric($stackPtr, 'Heredoc contains interpolation or expression', 'no');
+
+ $warning = 'Detected heredoc without interpolation or expressions. Use nowdoc syntax instead';
+
+ $fix = $phpcsFile->addFixableWarning($warning, $stackPtr, 'Found');
+ if ($fix === true) {
+ $identifier = trim(ltrim($tokens[$stackPtr]['content'], '<'));
+ $replaceWith = "'".trim($identifier, '"')."'";
+ $replacement = str_replace($identifier, $replaceWith, $tokens[$stackPtr]['content']);
+ $phpcsFile->fixer->replaceToken($stackPtr, $replacement);
+ }
+
+ }//end process()
+
+
+}//end class
diff --git a/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc b/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc
new file mode 100644
index 0000000000..abaad16d63
--- /dev/null
+++ b/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc
@@ -0,0 +1,108 @@
+bar}
+END;
+
+$heredoc = <<< "END"
+some ${beers::softdrink}
+END;
+
+$heredoc = <<< END
+{${$object->getName()}} text
+END;
+
+$heredoc = <<<"END"
+some {${getName()}}
+END;
+
+$heredoc = <<baz()()}
+END;
+
+$heredoc = <<values[3]->name} text
+END;
+
+$heredoc = <<<"END"
+some ${$bar}
+END;
+
+$heredoc = <<bar} text
+END;
+
+$heredoc = <<<"END"
+${foo["${bar}"]} text
+END;
+
+$heredoc = <<{${'a'}}} text
+END;
+
+$heredoc = <<{$baz[1]}}
+END;
+
+$heredoc = <<john's wife greeted $people->robert.
+END;
+
+$heredoc = <<bar}
+END;
+
+$heredoc = <<< "END"
+some ${beers::softdrink}
+END;
+
+$heredoc = <<< END
+{${$object->getName()}} text
+END;
+
+$heredoc = <<<"END"
+some {${getName()}}
+END;
+
+$heredoc = <<baz()()}
+END;
+
+$heredoc = <<values[3]->name} text
+END;
+
+$heredoc = <<<"END"
+some ${$bar}
+END;
+
+$heredoc = <<bar} text
+END;
+
+$heredoc = <<<"END"
+${foo["${bar}"]} text
+END;
+
+$heredoc = <<{${'a'}}} text
+END;
+
+$heredoc = <<{$baz[1]}}
+END;
+
+$heredoc = <<john's wife greeted $people->robert.
+END;
+
+$heredoc = <<bar}
+ END;
+
+$heredoc = <<< "END"
+ some ${beers::softdrink}
+ END;
+
+$heredoc = <<< END
+ {${$object->getName()}} text
+ END;
+
+$heredoc = <<<"END"
+ some {${getName()}}
+ END;
+
+$heredoc = <<baz()()}
+ END;
+
+$heredoc = <<values[3]->name} text
+ END;
+
+$heredoc = <<<"END"
+ some ${$bar}
+ END;
+
+$heredoc = <<bar} text
+ END;
+
+$heredoc = <<<"END"
+ ${foo["${bar}"]} text
+ END;
+
+$heredoc = <<{${'a'}}} text
+ END;
+
+$heredoc = <<{$baz[1]}}
+ END;
+
+$heredoc = <<john's wife greeted $people->robert.
+ END;
+
+$heredoc = <<bar}
+ END;
+
+$heredoc = <<< "END"
+ some ${beers::softdrink}
+ END;
+
+$heredoc = <<< END
+ {${$object->getName()}} text
+ END;
+
+$heredoc = <<<"END"
+ some {${getName()}}
+ END;
+
+$heredoc = <<baz()()}
+ END;
+
+$heredoc = <<values[3]->name} text
+ END;
+
+$heredoc = <<<"END"
+ some ${$bar}
+ END;
+
+$heredoc = <<bar} text
+ END;
+
+$heredoc = <<<"END"
+ ${foo["${bar}"]} text
+ END;
+
+$heredoc = <<{${'a'}}} text
+ END;
+
+$heredoc = <<{$baz[1]}}
+ END;
+
+$heredoc = <<john's wife greeted $people->robert.
+ END;
+
+$heredoc = <<
+ * @copyright 2024 PHPCSStandards and contributors
+ * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Standards\Generic\Tests\Strings;
+
+use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
+
+/**
+ * Unit test class for the UnnecessaryHeredoc sniff.
+ *
+ * @covers \PHP_CodeSniffer\Standards\Generic\Sniffs\Strings\UnnecessaryHeredocSniff
+ */
+final class UnnecessaryHeredocUnitTest extends AbstractSniffUnitTest
+{
+
+
+ /**
+ * Returns the lines where errors should occur.
+ *
+ * The key of the array should represent the line number and the value
+ * should represent the number of errors that should occur on that line.
+ *
+ * @return array
+ */
+ public function getErrorList()
+ {
+ return [];
+
+ }//end getErrorList()
+
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * The key of the array should represent the line number and the value
+ * should represent the number of warnings that should occur on that line.
+ *
+ * @param string $testFile The name of the file being tested.
+ *
+ * @return array
+ */
+ public function getWarningList($testFile='')
+ {
+ $warnings = [
+ 100 => 1,
+ 104 => 1,
+ ];
+
+ switch ($testFile) {
+ case 'UnnecessaryHeredocUnitTest.1.inc':
+ return $warnings;
+
+ case 'UnnecessaryHeredocUnitTest.2.inc':
+ if (PHP_VERSION_ID >= 70300) {
+ return $warnings;
+ }
+
+ // PHP 7.2 or lower: PHP version which doesn't support flexible heredocs/nowdocs yet.
+ return [];
+
+ default:
+ return [];
+ }
+
+ }//end getWarningList()
+
+
+}//end class