andymur
andymur

Reputation: 157

Compile package classes with different Java versions

I have a distributed application, one part of it is legacy thus running under Java 7.

Another part is running (and of course compiled) under Java 8.

So calling service of the second VM from first part of App ends with:

Exception in thread "main" java.lang.UnsupportedClassVersionError: Unsupported major.minor version 52.0

I decided to compile needed service with Java 7 source/target using Maven compiler plugin.

So, imagine we have a package with two classes:

com/example/A.java
com/example/B.java

How to configure Maven compiler plugin, or is there another way? To compile A.java with Java 7 and other (B.java) with Java 8.

Upvotes: 3

Views: 1783

Answers (2)

Pierre B.
Pierre B.

Reputation: 12923

Disclaimer: @GhostCat answer provides proper guidance, the real issue is in the delivery and packaging process not in the compilation. Though strongly not recommended, it is possible to do what is asked. For the sport of it, I'll try to specifically provide an answer to the question:

How to configure maven compiler plugin to compile A.java with Java7 and other (B.java) with Java8.

You can define multiple Maven Compiler Plugin executions, each one including or excluding your desired set of classes and using different source/target version. For example using profiles:

<profiles>
    <profile>
        <id>compile-java7</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                    <executions>
                        <execution>
                            <id>default-compile</id>
                            <goals>
                                <goal>compile</goal>
                            </goals>
                            <configuration>
                                <source>1.7</source>
                                <target>1.7</target>
                                <includes>
                                    <include>**/*Java7.java</include>
                                </includes>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>compile-java8</id>
        <build>
           <!-- define similar execution for java 8 -->
        </build>
    </profile>
</profiles>

Using profile you'll have to run your build twice (using mvn ... -Pcompile-java7 and son on), once for each Java version. You can also do similar setup allowing to run the build only once, such as defining execution with an output folder for Java 7 and Java 8, etc. The idea is to define different executions using different source and target environment with proper inclusion or exclusion.

For example, to output each classes versions under separate folders:

  • Skip the default compiler execution
  • Define a Java 7 and Java 8 execution with the -d flag for output directory
  • As javac cannot create folder, you'll have to make sure they are generated (for example using the AntRun plugin)

You'll end-up with something like:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <executions>
                <execution>
                    <id>default-compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <skipMain>true</skipMain>
                    </configuration>
                </execution>
                <execution>
                    <id>compile-java7</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                        <compilerArgs>
                            <arg>-d</arg>
                            <arg>${build.outputDirectory}/java7</arg>
                        </compilerArgs>
                        <includes>
                            <include>**/*Java7.java</include>
                        </includes>
                    </configuration>
                </execution>
                <execution>
                    <id>compile-java8</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <compilerArgs>
                            <arg>-d</arg>
                            <arg>${build.outputDirectory}/java8</arg>
                        </compilerArgs>
                        <includes>
                            <include>**/*Java8.java</include>
                        </includes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.8</version>
            <executions>
                <execution>
                    <phase>validate</phase>
                    <configuration>
                        <tasks>
                            <echo message="Creating test output directory"/>
                            <mkdir dir="./target/classes/java7"/>
                            <mkdir dir="./target/classes/java8"/>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Note that such practice, though possible, is still a bad idea:

  • Problem will be better answered by reviewing the delivery methods and architecture for your project as pointed out by GhostCat
  • You over complexify your build (that's plain plugin configuration torture, that is!)
  • You override most of Maven default behavior

Upvotes: 3

GhostCat
GhostCat

Reputation: 140417

You are going down the wrong rabbit hole here.

Do not think of your product/deploy unit/delivery in terms of package boundaries. Packages are really something that help you organizing source code. Nothing else.

Thinking about different versions for different packages is simply: spending time on the wrong question.

Instead: understand your deliveries and then, from there on decide which product should be compiled in what version.

Beyond that: using a service can be much more than just invoking some other class. If you really have different components that run independently of each other, but that have to use each other: then define those services, and use defined protocols to interconnect them.

For example: JVM A provides a REST API, and JVM B uses that to invoke a service provided/hosted by JVM A. Then you are no longer forced to think about Java versions - instead you focus on clearly defining your interfaces and the ways to use them. Or use some sort of message bus. There are plenty of options to choose from. As said: use your time and energy to think about that, instead of controlling java versions on package boundaries.

My suggestions sound like overkill: but alone the fact that you have JVMs running different versions indicate that you are already looking at a more complex, "real world" scenario. And in complex real world scenarios, you better turn to real world solutions (like service definitions) instead of hacking your way around them.

Upvotes: 7

Related Questions